I want to add text (eg. Hi) in a shape (eg. Square) in SwiftUI and make them act as a single object.
It looks like there's no direct way to add text in shape in SwiftUI.
Here is what I consider to be a more comprehensive answer. This will work as of Xcode 11.5:
Text(question)
.fixedSize(horizontal: false, vertical: true)
.multilineTextAlignment(.center)
.padding()
.frame(width: 300, height: 200)
.background(Rectangle().fill(Color.white).shadow(radius: 3))
Notes:
fixedSize() will let the text wrap (since .lineLimit(nil) no longer is working). You can omit it if you simply want one line of text with ellipsis
multilineTextAlignment() allows you to center or align the text in any way
padding() gives the text more space to breathe within the Rectangle()
frame() sets the width and height of the Text() and hence, the Rectangle(), since it is the background of the Text()
background() sets the shape of the Text()'s background. I have added a fill color and a drop shadow here as well
The end result of this example is the text looks to appear within a cue card like shape!
Here is, IMO, most simple approach:
Generic schema
Text(_any_of_text_)
.background(_any_of_Shape)
eg:
Text("Hello, World!")
.background(Rectangle().stroke())
Text("Hello, World!")
.background(RoundedRectangle(cornerRadius: 4).stroke())
Using Swift built-in shapes such as Capsule(), RoundedRectangle() and etc. Then, you can apply .overlay to the shape. The overlay take in any view such as text.
Example:
var body: some View {
Capsule()
.fill(Color.blue)
.overlay(
Text("Hello World")
)
}
Outcome:
Text("Hi")
.frame(width: 40, height: 40, alignment: .center)
.background(Color.black)
.clipShape(Rectangle())
Create a new SwiftUI View and make use of a Z-Stack to create your goal.
struct YourCustomObject: View {
var body: some View {
ZStack {
Rectangle()
.fill(Color.secondary)
.frame(width: 200, height: 200)
Text("Your desired text")
.foregroundColor(.white)
}
}
}
Related
I have a ZStack in which an Image is presented.
The image needs to be scaled in some cases.
I am failing on aligning the scaled image on the bottom of the ZStack, it is always presented in the middle.
I tried ZStack(alignment: .bottom) in combination with .alignmentGuide(.bottom) for the image, but this does not change the outcome.
Also putting a VStack around the image and placing a Spacer() above it does not change the result.
The HStack is not relevant and is only shown, because I need an ZStack in this construct. But The main issue is with the VStack, that it does not move after scaling in the Space of the ZStack.
It seems like .scaleEffect just uses position and frame of the original image and places the scaled image in the middle. Is this a limitation of scaleEffect? What other function can be used?
This is my View (reduced code): // I colored the background purple, to show the full size of the ZStack
var body: some View {
ZStack(alignment: .bottom) {
Color.purple
Image(battlingIndividual.getMonster().getStatusImageName(battlingIndividual.status))
.resizable()
.scaledToFill()
.scaleEffect(battlingIndividual.getMonster().size.scaleValue)
HStack() {
SkillViews(battlingIndividual: battlingIndividual)
Spacer()
}
}
}
The outcome is this:
But it should look like this:
EDIT: I added a Background to the image, in order to show that the image is centered in the ZStack.
Solution:
We don´t need an alignment in this case, we need an anchor:
.scaleEffect(battlingIndividual.getMonster().size.scaleValue, anchor: .bottom)
Solution Image:
I figured it out.
.scaleEffect uses its own anchor, which can be set to .bottom.
scaleEffect(_:anchor:) Apple Developer
Therefore I needed only to add "ancor: .bottom" to the scaleEffect.
.scaleEffect(battlingIndividual.getMonster().size.scaleValue, anchor:
.bottom)
for the following result:
I assume this view container ZStack is a one cell view, so you need to align not ZStack which tights to content, but entire HStack containing those monster cells, like
HStack(alignment: .bottom) { // << here !!
ForEach ... {
MonsterCellView()
}
}
Please, put your Image in a VStack and a Spacer() above the image and your Images will be on the bottom of the Stack. The alignment .bottom is only to aline multiple views with each other, but you are not having multiple views in your Stack. The HStack doesn't count for the alignment.
If I try this out in my example and scale the image down,
import SwiftUI
struct ContentView: View {
var body: some View {
ZStack {
Color.purple
VStack {
Spacer()
Image(systemName: "ladybug")
.resizable()
.scaledToFit()
.frame(width: 100, height: 100, alignment:
HStack {
Image(systemName: "hare")
.resizable()
.frame(width: 50, height: 50)
Image(systemName: "hare")
.resizable()
.frame(width: 50, height: 50)
Image(systemName: "hare")
.resizable()
.frame(width: 50, height: 50)
Spacer()
}
}
}
}
}
I get this.
Kind regards,
MacUserT
I'm trying to figure out the basics of ScrollViews in SwiftUI.
I figured if I created a Text with a frame of the width of the screen and .infinite height, which I understood to mean "as large as the available space, e.g. safe area", and dropped it into a ScrollView with another Text companion, I'd get a screen-sized Text that could scroll horizontally to the companion Text.
struct ContentView: View {
var body: some View {
ScrollView(.horizontal, showsIndicators: false) {
GeometryReader { geometry in
VStack {
Text("crash me")
}
.frame(width: geometry.size.width,
height: .infinity,
alignment: .topLeading)
}
Text("crash me")
}
}
}
If I run this, it just crashes. What's so stupid about it?
Instead of:
.frame(height: .infinity)
You should use:
.frame(maxHeight: .infinity)
Setting the exact height to .infinity is not possible, but maxHeight means that the view will stretch its height to the maximum value possible. This would be the size of the screen, or any other limiting factor set by the parent.
I am trying to build my own custom tab bar view, while building my custom buttons I am unable to change the color of Image().
struct TabBarButton: View {
let title: String
let icon: String
var body: some View {
return GeometryReader{ geometry in
VStack {
Image(self.icon)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: geometry.size.width/2, height: CGFloat(25))
.foregroundColor(.white)
Text(self.title)
.font(.system(size: 8))
.foregroundColor(Color.white)
}
}
}
}
I have tried foregroundColor(Color.white), accentColor(Color.white), as well as some different color multipliers. Is there a reason the color isn't anything other than default black? Easy fix is just to get white icons but I was hoping to solve this.
I'm not sure what are you trying to to achieve, but probably you just need template rendering mode, like
Image(self.icon)
.renderingMode(.template)
.foregroundColor(.white)
In assets on import icon (import from *.pdf), set up Image Set → Render as → Template Image
In the code:
Image("ImageFileName")
Or:
You can add the "Template" suffix and set "Rednder as default". In documentation
if the name of the image ends in "Template", use the image as a
template, otherwise render it as the original image
and in code:
Image("ImageFileNameTemplate")
Single Color Images (like icons and symbols)
For setting the color to single-color images with the foregroundColor modifier, you should make sure that image renderingMode is set to template
with code:
Image("Star")
.renderingMode(.template)
.foregroundColor(.yellow)
or
With Assets Catalogue properties:
Note that the image MUST have the alpha channel (like PNG or PDF), otherwise you will get a colored rectangle!
Multi-Color Symbols
From iOS 15, Apple introduced symbols with more rendering mode support. you can create one or change one and export it from the SF Symbols.app:
And you can also use up to 3 colors directly in the code like:
Recolor a Colorful Image!
SwiftUI is so easy and powerful and you can recolor an image in no time with a few modifiers like the blendingMode:
You are making something that should look and behave like button. Why not make it a button right from the start, so you can reuse such kind of buttons wherever you like?
struct TabBarButton: ButtonStyle {
var icon: String = "" // you a free to provide a reasonable default
func makeBody(configuration: Self.Configuration) -> some View {
GeometryReader{ geometry in
VStack {
Image(systemName: icon)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: geometry.size.width/2, height: CGFloat(25)) // I didn't put therse magic numbers here
.foregroundColor(.green) // not very stylish but the color is a at your control
configuration.label
.font(.system(size: 8)) // and here
.foregroundColor(Color.black)
}
}
}
}
Use as usual button with title, action {}.
Button("Tab Bar Button") {
// action code here
}
.buttonStyle(TabBarButton(icon: "info.circle"))
Here is an example of adding custom button to Navigation Bar:
struct TabBarButton: PrimitiveButtonStyle {
var icon: String = "" // you a free to provide a reasonable default
func makeBody(configuration: Self.Configuration) -> some View {
VStack {
Image(systemName: icon)
.resizable()
.aspectRatio(contentMode: .fit)
.foregroundColor(.green) // Changes color in NavBar
configuration.label
.foregroundColor(Color.black)
}
}
}
And in ContentView add:
.navigationBarItems(trailing: Button(action: {}, label: {
Text("Some")
}).buttonStyle(TabBarButton(icon: "info.circle")))
The result looks like this:
iOS 15+
Some symbols has multiple layers. You need to use .symbolRenderingMode(.palette) and set the color of each of the layers using .foregroundStyle() view modifier explicitly
Image(systemName: "cloud.sun.fill")
.symbolRenderingMode(.palette)
.foregroundStyle(.black, .yellow)
this code will add any color to the image just change the name with color-name
Image("Name")
.resizable()
.foregroundColor(Color.name)
Here the code:
Image("profile_photo").resizable()
.renderingMode(.template)
.foregroundColor(.blue)
.scaledToFill()
.frame(width: 120, height: 120,alignment: .center)
.clipShape(Circle())
you just need template rendering mode. nothing else
Image(your image)
.renderingMode(.template)
.do anything you need to update
Added on the 24th of July:
This line of code fixes the space in the detail view. However... in the list view the title has become a lot smaller too.
.navigationBarTitle(Text("Egg management"), displayMode: .inline)
Added on the 23th of July:
Thanks to the tips I made a lot of progress. Especially the tip to add borders does wonders. You see exactly what happens!
However, there seems to be a difference between the Xcode Preview canvas, the simulator and the physical device. Is this a bug because -after all- it is still beta? Or is there anything I can do?
As you can see in the images... only in the Xcode Preview canvas the view connects to the top of the screen.
I believe it has something to do with the tabbar. Since when I look at the Xcode Preview canvas with the tabbar... that space above is also there. Any idea how to get rid of that?
Original postings:
This is my code for a detailed list view:
import SwiftUI
struct ContentDetail : View {
#State var photo = true
var text = "Een kip ..."
var imageList = "Dag-3"
var day = "3.circle"
var date = "9 augustus 2019"
var imageDetail = "Day-3"
var weight = "35.48"
var body: some View {
VStack (alignment: .center, spacing: 10) {
Text(date)
.font(.title)
.fontWeight(.medium)
ZStack (alignment: .topLeading){
Image(photo ? imageDetail : imageList)
.resizable()
.aspectRatio(contentMode: .fit)
.background(Color.black)
.padding(.trailing, 0)
.tapAction {
self.photo.toggle() }
HStack {
Image(systemName: day)
.resizable()
.padding(.leading, 10)
.padding(.top, 10)
.frame(width: 40, height: 32)
.foregroundColor(.white)
Spacer()
Image(systemName: photo ? "photo" : "pencil.circle")
.resizable()
.padding(.trailing, 10)
.padding(.top, 10)
.frame(width: 32, height: 32)
.foregroundColor(.white)
}
}
Text(text)
.lineLimit(6)
.frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
.padding(.leading, 6)
} .padding(20)
}
}
#if DEBUG
struct ContentDetail_Previews : PreviewProvider {
static var previews: some View {
ContentDetail()
}
}
#endif
Also included is the preview canvas. What I don't get is how I can make sure the text and photo are aligned to the top (instead of the middle). I tried with Spacers, padding etc.
I must be overseeing something small I guess... but. Can somebody point me in the right direction? Thanks.
Added:
After both answers I added a Spacer() after the last text. In Xcode in the preview canvas everything looks okay now. But on my connected iPhone 7 Plus there are some problems: the view is not aligned to the top, and the image is cropped (icon on the right is gone; white banding to the right).
Adding a Spacer() after the last text shifts everything to the top. Tested on iPhone Xr simulator (not preview).
...
Text(text)
.lineLimit(6)
.frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
.padding(.leading, 6)
Spacer()
}
To remove the space at the top:
VStack {
...
}
.padding(20)
.navigationBarTitle("TITLE", displayMode: .inline)
Think in terms of what a Spacer() does. It "moves" the views as far apart as it can - at least, without a specific space.
So you have this:
VStack {
Text
ZStack {
Image
HStack {
Image
Spacer()
Image
}
}
Text
}
All told, going from inner to outer, you have a horizontal stack of two images placed as far apart (the spacer is between them) inside of a "Z axis" stack that places an image on top of them, inside of a vertical stack that has some text above it.
So if you want to move everything in that vertical stack to the top, you simply need to add one last spacer:
VStack {
Text
ZStack {
Image
HStack {
Image
Spacer()
Image
}
}
Text
Spacer() // <-- ADD THIS
}
Last note: Don't be afraid to adding additional "stacks" to your view. In terms of memory footprint, it's really just a single view with no performance hit.
EDIT: I took your original view and changed everything to placeholders...
var body: some View {
VStack (alignment: .center, spacing: 10) {
Text("Text #1")
.font(.title)
.fontWeight(.medium)
ZStack (alignment: .topLeading) {
Text( "Image #1")
HStack {
Text("Image #2")
Spacer()
Text("Image #3")
}
}
Text("Text #2")
.lineLimit(6)
.frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
.padding(.leading, 6)
} .padding(20)
}
As expected, everything is vertically centered. Adding a Spacer() below "Text #2" throws everything to the top. A couple of thoughts:
Starting there, and add in your Image views one by one. Add in the modifiers like that also.
I don't have the specific images you are rendering, so maybe put a noticeable background color on various things (orange is my personal favorite) and see if the top Image is actually on top but the image makes it appear as though it isn't. A border would work pretty well too.
I am trying to make an image fill the screen (including the safe area under under the notch of the iPhone X series and near the app switcher bar).
import SwiftUI
struct ContentView : View {
var body: some View {
ZStack() {
Image("background")
.resizable()
.aspectRatio(UIImage(named: "background")!.size, contentMode: .fill)
Text("SwiftUI Example")
.font(.largeTitle)
.background(Color.black)
.foregroundColor(.white)
}
}
}
The above code produces the following result, which still has the white bars on the top and bottom. Is there a way to make the image truly fill the screen?
SwiftUI by default takes the safe areas into consideration. In order to get the desired result, you must inform SwiftUI that it can ignore the safe areas with the following statement appended to the last object:
.edgesIgnoringSafeArea(.all)
This means that the new code will be:
ZStack() {
Image("background")
.resizable()
.aspectRatio(UIImage(named: "background")!.size, contentMode: .fill)
Text("SwiftUI Example")
.font(.largeTitle)
.background(Color.black)
.foregroundColor(.white)
}.edgesIgnoringSafeArea(.all)