How can I make an SF symbol horizontally align centered in SwiftUI? - ios

I have a SwiftUI Image view that displays only an SF symbol:
GeometryReader { g in
Image(systemName: "a.square")
.font(.system(size: g.size.width))
}
}
It renders like this:
I tried modifying the view with the .multilineTextAlignment(.center) modifier to center it, and that didn't do anything.

You can try adding this command line for it:
.position(x: g.size.width / 2, y: g.size.width / 2)
The code you wrote looks like this:
GeometryReader { g in
Image(systemName: "a.square")
.font(.system(size: g.size.width))
.position(x: g.size.width / 2, y: g.size.width / 2)
}
}

Why not the declarative way?
VStack {
Image(systemName: "a.square")
.resizable()
.aspectRatio(1.0, contentMode: .fit)
.padding()
Spacer()
}

Related

SwiftUI - How to align elements in left, center, and right within HStack?

I'm creating an iOS minesweeper game and want to have a bar at the top with three pieces of information:
The high score (next to a trophy symbol) [LEFT]
The current time (next to a timer symbol) [CENTER]
The number of bombs left (next to a bomb symbol) [RIGHT]
However, the elements are not evenly aligned as you can see in the picture at the bottom- I'm guessing the spacers are automatically the same size. So, because the text on the left side of the screen is wider than that on the right, the middle text is offset to the right.
How can I align these items so the center time/image is perfectly in the middle with the other objects on the left/right?
My code looks like:
struct GameView: View {
#EnvironmentObject var game: Game
#State var labelHeight = CGFloat.zero
var body: some View {
VStack(alignment: .center, spacing: 10) {
Text("MINESWEEPER")
.font(.system(size: 25, weight: .bold, design: .monospaced))
.kerning(3)
.foregroundColor(.red)
HStack(alignment: .center, spacing: 5) {
Image(systemName: "rosette")
.resizable()
.aspectRatio(contentMode: .fit)
.foregroundColor(.black)
Text(game.highScoreString)
.font(.system(size: 15, weight: .bold, design: .monospaced))
.foregroundColor(.black)
Spacer()
Image(systemName: "timer")
.resizable()
.aspectRatio(contentMode: .fit)
.foregroundColor(.black)
Text(game.timerString)
.font(.system(size: 15, weight: .bold, design: .monospaced))
.foregroundColor(.black)
Spacer()
Image("top_bomb")
.resizable()
.aspectRatio(contentMode: .fit)
Text(String(game.bombsRemaining))
.font(.system(size: 15, weight: .bold, design: .monospaced))
.foregroundColor(.black)
.overlay(GeometryReader(content: { geometry in
Color.clear
.onAppear(perform: {self.labelHeight = geometry.frame(in: .local).size.height})
}))
}
.frame(width: UIScreen.main.bounds.width * 0.9, height: labelHeight, alignment: .center)
BoardView()
}
}
}
Instead of aligning by spacers, move each part into separate view, say LeftHeader, CenterHeader, and RightHeader and use frame alignment, which gives exact separation by equal sizes, like
HStack {
LeftHeader()
.frame(maxWidth: .infinity, alignment: .leading)
CenterHeader()
.frame(maxWidth: .infinity, alignment: .center)
RightHeader()
.frame(maxWidth: .infinity, alignment: .trailing)
}
This can be achieved using a ZStack , HStack and Spacers.
ZStack{
HStack{
Text("Left")
Spacer()
}
HStack{
Text("Center")
}
HStack{
Spacer()
Text("Right")
}
}
Even if you remove left ( or right ) HStack, it won't affect the other components or the layout.
You can make it look bit nicer by adding paddings like this,
ZStack{
HStack{
Text("Left").padding([.leading])
Spacer()
}
HStack{
Text("Center")
}
HStack{
Spacer()
Text("Right").padding([.trailing])
}
}
I think you can create more one stack.
My solution like:
/**
* spacing is an example
* minWidth calculated by fontSize
*/
HStack(alignment: .center, spacing: 20){
HStack{ Image, Text }.frame(minWidth: 30)
HStack{ Image, Text }.frame(minWidth: 30)
HStack{ Image, Text }.frame(minWidth: 30)
}
Hope it useful for you.

SwiftUI number of lines for Text element causing random margin behavior

I'm having a bit of trouble with this particular view. For some reason, it seems like depending on whether the Text(title) element is one line or two lines, I'm getting very odd behavior for the total height of the view. All the elements are dynamically populated so I can't just have a set height. I've ruled out that this is issue not dependent on the image that's loaded, but always occurs when the Text(title) element is one line. As you can see, when the element is only one line, it causes the image to have a padding above it and the 'See more' button to have a padding below it. The first image shows the margin issue (single line for title) and the second shows how the margins aren't there for multi-line title. Any help much appreciated!
https://i.stack.imgur.com/MB6Dt.jpg
https://i.stack.imgur.com/RDKOj.jpg
VStack(alignment: .leading) {
Image(systemName: "camera")
.data(url: URL(string: picture)!)
.resizable()
.scaledToFill()
.frame(width: UIScreen.main.bounds.width * 0.8)
.maxHeight(215)
.clipped()
Group {
HStack {
Text(title)
.bold()
.font(.system(size: 20, weight: .bold))
.padding(.vertical, 5)
Spacer()
}
HStack {
Text(subInformation)
.font(.system(size: 17, weight: .semibold))
.foregroundColor(Color.red)
.padding(.bottom, 5)
Spacer()
}
HStack {
Text(description)
.padding(.bottom, 2)
.foregroundColor(Color.gray)
Spacer()
}
}.padding(.horizontal)
DestinationAd()
.frame(width: UIScreen.main.bounds.width * 0.8, height: 50)
.padding(.bottom, 5)
Link(destination: URL(string: urlToOpen)!, label: {
HStack {
Spacer()
Text("See more")
.padding(.vertical, 5)
Spacer()
}.background(Color.blue)
.cornerRadius(10)
}).padding(.horizontal, 10)
.padding(.bottom, 10)
}.frame(width: UIScreen.main.bounds.width * 0.8)
.background(Color.white)
.cornerRadius(20)
I think that everything works as expected in your example. You are populating VStack with some child elements. VStack wraps it's content by default. In the first case (when the text only has one line) child views take less space that in the second example.
If you don't want to set frame for your VStack, maybe Spacer after the image and before the button could help.

SwiftUI ZStack takes all screen height but should be fixed height

My code:
public var body: some View {
ZStack(alignment: .bottom) {
Ellipse()
.fill(.yellow)
Text("Text")
.padding(.bottom, 42)
.foregroundColor(.red)
}
.frame(width: 546, height: 364)
.position(x: UIScreen.main.bounds.size.width / 2, y: Spacing.padding_0_5)
.edgesIgnoringSafeArea(.top)
.background(Color.red)
}
makes ZStack takes almost all screen height. But I expect it will take height from .frame() method.
I have a workaround for you, it's a bit messed up but works
public var body: some View {
ZStack {
ZStack(alignment: .bottom) {
Ellipse()
.fill(.yellow)
}
.frame(width: 546, height: 364)
.position(x: UIScreen.main.bounds.size.width / 2, y: Spacing.padding_0_5)
.edgesIgnoringSafeArea(.top)
.zIndex(0)
ZStack {
VStack {
VStack {
Text("Text")
.padding(.top, 42)
.foregroundColor(.red)
}
.frame(width: UIScreen.main.bounds.width, height: 182)
VStack {
Text("Your texts here")
}
.frame(width: UIScreen.main.bounds.width)
Spacer()
}
.frame(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height)
}
.zIndex(1)
}
.background(Color.red)
}
I simply made your ellipse on another layer and text on the other.
ZStack(alignment: .bottom) {
Ellipse()
.fill(.yellow)
}
.frame(width: 546, height: 364)
.position(x: UIScreen.main.bounds.size.width / 2, y: Spacing.padding_0_5)
.edgesIgnoringSafeArea(.top)
.zIndex(0)
The .zIndex(0) makes sure that the view is in the background.
ZStack {
VStack { // This VStack contains all your text
VStack { // First VStack
Text("Text")
.padding(.top, 42)
.foregroundColor(.red)
}
.frame(width: UIScreen.main.bounds.width, height: 182)
VStack { //Second VStack
Text("Your texts here")
}
.frame(width: UIScreen.main.bounds.width)
Spacer()
}
.frame(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height)
}
.zIndex(1)
Here, the ZStack takes up the entire screen. We added a VStack which contains your texts.
The first VStack has your main label over the Ellipse, and its frame is hardcoded according to the height of the Ellipse (1/2 the height as the other half of the ellipse is outside the screen).
The second VStack starts from the end of our first VStack which was the functionality needed, finally added a spacer() so that the text is placed at the top rather than middle.
The zIndex(1) makes sure that is placed over the elements at zIndex(0)

rounded rectangle as a background in swiftUI

I tried to do an overlay, but it doesn't work ,
That part on the bottom keeps bugging me.
is there any other method beside doing an overlay of a rectangle??
ZStack {
RoundedRectangle(cornerRadius: 15)
.foregroundColor(Color("Hello"))
.frame(height: 700, alignment: /*#START_MENU_TOKEN#*/.center/*#END_MENU_TOKEN#*/)
.overlay(
VStack {
RoundedRectangle(cornerRadius: 15)
.frame(width:381, height: 130.36, alignment: /*#START_MENU_TOKEN#*/.center/*#END_MENU_TOKEN#*/)
.shadow(radius: 4,y:3)
.foregroundColor(.white)
.overlay(
HStack {
Button(action: /*#START_MENU_TOKEN#*//*#PLACEHOLDER=Action#*/{}/*#END_MENU_TOKEN#*/) {
ButtonBig(ButtonText: "Keuangan",ButtonIcon: "creditcard.fill")
}
Button(action: /*#START_MENU_TOKEN#*//*#PLACEHOLDER=Action#*/{}/*#END_MENU_TOKEN#*/) {
ButtonBig(ButtonText: "Penjurusan",ButtonIcon: "figure.walk")
}
Button(action: /*#START_MENU_TOKEN#*//*#PLACEHOLDER=Action#*/{}/*#END_MENU_TOKEN#*/) {
ButtonBig(ButtonText: "Status",ButtonIcon: "person.crop.circle.badge.plus")
}
}.buttonStyle(PlainButtonStyle()))
RoundedRectangle(cornerRadius: 15)
.frame(width:381, height: 535, alignment: /*#START_MENU_TOKEN#*/.center/*#END_MENU_TOKEN#*/)
.shadow(radius: 4,y:3)
.foregroundColor(.white)
}
I wanted to create something like that basically
if there's a library for it, please do tell me. Thank you
Try these 2 modifiers:
.background(RoundedRectangle(cornerRadius: 10))
.clipped()

How do I create a leading border for a view with cornerRadius in SwiftUI?

So I've got a view
VStack(alignment: .leading) {
Text("Top text").font(.title2).bold()
Spacer()
Text("Bottom text")
}
.padding()
.background(Color.white)
.cornerRadius(10)
.shadow(radius: 5)
and want to setup a left colored (e.g., in red) border that should match its rounded contour.
I tried adding:
.overlay(RoundedRectangle(cornerRadius: 20)
.frame(width: 2, height: nil, alignment: .leading), alignment: .leading)
that does draw a left border but it doesn't match a rounded contour:
and I'd like to get something like this instead:
Here is a demo of possible approach - using mask in overlay (also shown how to make button clickable if there will be some in card)
Tested with Xcode 12.1 / iOS 14.1
struct DemoView: View {
var body: some View {
VStack(alignment: .leading) {
Text("Top text").font(.title2).bold()
Spacer()
Button("Bottom text") {}
}
.padding()
.background(Color.white)
.cornerRadius(10)
.shadow(radius: 5)
.overlay(
RoundedRectangle(cornerRadius: 10).fill(Color.red).mask( // << here !!
HStack {
Rectangle().frame(width: 10)
Spacer()
}
).allowsHitTesting(false)) // << make click-through
.frame(height: 200)
}
}

Resources