SwiftUI Custom Navigation Bar VStack doesn't work - ios

I'm trying to make a custom navigation bar with back button, image, VStack (2 labels) but it didn't work. The whole view will stick to the center and not following the alignment I set. Thank you!
struct WeatherNavigation: View {
var body: some View {
HStack {
WeatherNavigation()
}
.
.
.
}
}
//
struct WeatherNavigation: View {
var body: some View {
Button(action: {
//action
}, label: {
HStack {
Image("Back")
.foregroundColor(.black)
Image("Weather")
.resizable()
.frame(width: 40, height: 40)
}
})
.frame(width: 100, height: 50, alignment: .leading)
VStack {
Text(weather.description)
.font(.appFont(size: 18))
.foregroundColor(Color(uiColor: .black))
Text(weather.location)
.font(.appFont(size: 12))
.foregroundColor(Color(uiColor: .blue))
}
.frame(width: .infinity, height: 50, alignment: .leading)
}
}

First of all, you shouldn't set frame of the whole view like that. For you problem, it can divide into: make a HStack to store all the container view and make a space between those two of them. Because using HStack you don't need to add leading.
Code will be like this
struct WeatherNavigation: View {
var body: some View {
// make a HStack to store all the attribute
HStack(alignment: .top) {
Button(action: {
//action
}, label: {
HStack {
Image("Back")
.foregroundColor(.black)
Image("Weather")
.resizable()
.frame(width: 40, height: 40)
}
})
.frame(width: 100, height: 50)
VStack {
Text("Tokyo")
.foregroundColor(Color(uiColor: .black))
Text("Japan")
.foregroundColor(Color(uiColor: .blue))
}
// make a space at the end
Spacer()
}
}
}
And the usage like this
struct ContentView: View {
var body: some View {
VStack {
HStack {
WeatherNavigation()
}
Spacer()
}
}
}
More over: adding Spacer() means that make space between view. That will solved your problem if you want to keep like your way.

Related

How to make SwiftUI interface scalable?

My SwiftUI Interface looks different on each iPhone model. I want to ask for best practice of creating scalable(or at least adaptable) interface with SwiftUI.
For example:
struct NewStruct: View {
#State var someState = false
var body: some View {
VStack {
HStack {
Spacer().frame(width: 25)
Text("Text1")
Spacer()
}
Button(action: {
someState = !someState
}, label: {
ZStack {
RoundedRectangle(cornerRadius: 15).fill(Color.white).frame(width: 350, height: 60)
HStack {
HStack {
Spacer().frame(maxWidth: 10)
Image(systemName: "bolt.fill").resizable().scaledToFit().frame(width: 35, height: 35).cornerRadius(30).padding()
Text("Text1").foregroundColor(Color.black)
Spacer()
Spacer(minLength: 30)
Image(systemName: "poweroff").resizable().frame(width: 25, height: 25)
Image(systemName: "power").resizable().frame(width: 25, height: 25)
Spacer().frame(maxWidth: 30)
}
}
}
})
}
}
}
How it should look like (iPhone 12)
Buggy view(iPhone 12 Pro Max)
The problem is here — setting a fixed frame of 350 isn't going to scale well to other screen sizes:
RoundedRectangle(cornerRadius: 15).fill(Color.white).frame(width: 350, height: 60)
Also, instead of ZStack, use .background — ZStack are usually used for bigger views. Another thing you can do is use .padding instead of Spacer().frame(width: 25) — spacers are designed to expand to fill as much space as possible, so limiting its frame to 25 doesn't really make sense.
struct NewStruct: View {
#State var someState = false
var body: some View {
VStack {
HStack {
Text("Text1")
Spacer()
}
.padding(.horizontal, 25)
Button(action: {
someState = !someState
}, label: {
HStack {
HStack {
Image(systemName: "bolt.fill").resizable().scaledToFit().frame(width: 35, height: 35).cornerRadius(30).padding()
Text("Text1").foregroundColor(Color.black)
Spacer()
Image(systemName: "poweroff").resizable().frame(width: 25, height: 25)
Image(systemName: "power").resizable().frame(width: 25, height: 25)
}
.padding(.horizontal, 10)
}
.background( /// here!
RoundedRectangle(cornerRadius: 15).fill(Color.white)
)
})
}
}
}
struct ContentView: View {
var body: some View {
/// ZStacks are made for bigger views, like a red color that fills the screen.
ZStack {
Color.red
NewStruct()
}
}
}
Result:

SwiftUI: padding above my view, for no reason?

I am trying to create an Instagram-like UI with SwiftUI, and since I wasn't able to resize the tab elements in the TabView, I decided to write a simple CustomTabView instead. But I end up with a padding at the top of it and I don't understand why. Here is the code:
struct ContentView: View {
#State private var selectedIndex: Int = 0
var body: some View {
VStack {
switch selectedIndex {
case 0:
Color.blue
case 1:
Color.yellow
case 2:
Color.red
case 3:
Color.orange
default:
Color.green
}
CustomTabView(selectedIndex: $selectedIndex)
}
}
}
struct CustomTabView: View {
#Binding var selectedIndex: Int
var body: some View {
VStack {
Divider()
HStack {
Button(action: {
selectedIndex = 0
}, label: {
Image("HomeIcon")
.resizable()
.frame(width: 30, height: 30, alignment: .center)
})
Spacer()
Button(action: {
selectedIndex = 1
}, label: {
Image("PlayIcon")
.resizable()
.frame(width: 30, height: 30, alignment: .center)
})
Spacer()
Button(action: {
selectedIndex = 2
}, label: {
Image("AddIcon")
.resizable()
.frame(width: 30, height: 30, alignment: .center)
})
Spacer()
Button(action: {
selectedIndex = 3
}, label: {
Image("HeartIcon")
.resizable()
.frame(width: 30, height: 30, alignment: .center)
})
Spacer()
Button(action: {
selectedIndex = 4
}, label: {
Image("ProfileIcon")
.resizable()
.frame(width: 30, height: 30, alignment: .center)
.cornerRadius(15)
})
}
.padding(.horizontal, 24)
.padding(.top, 4)
}
.background(Color.white)
}
}
The result I'm getting:
What am I doing wrong?
Thank you for your help
If the problem is the gap between the blue box and the divider, try setting the spacing of the VStack to 0:
VStack(spacing: 0) {
...
}
Top Space
That is the safe area. You can ignore it with this modifier:
.ignoresSafeArea()
Apply it to the view you want it to extend beyond the safe area, for example:
Color.blue
.ignoresSafeArea(.container, edges: .top)
Bottom Space
That is the spacing of the VStack. Get rid of it by setting that to 0:
VStack(spacing: 0) {
,,,
}
try this
Image("background").resizable()
.scaledToFill()
.clipped()
.edgesIgnoringSafeArea([.top])

SwiftUI vertical ScrollView springs back up and doesn't show all the views within it

Im working on a project with a similar layout to this where I have a few views stacked within a VStack and a button on the bottom, all embedded in a scrollview.
as you can see in the image the scrollview springs back and doesn't show the button.
var body: some View {
VStack {
Rectangle()
.fill(Color.green)
.frame(height: 300)
Spacer()
ScrollView {
RectanglesView()
Button(action: /*#START_MENU_TOKEN#*/{}/*#END_MENU_TOKEN#*/) {
Text("Click me")
}
.offset(y: 50)
.frame(width: 300)
}
}.edgesIgnoringSafeArea(.all)
}
I do believe that the issue is due to the button's offset since it behaves normally if I remove it, but I don't want to lose the placement of the button.
Instead of offset, add padding, like this and within a VStack (slightly amended code below)
var body: some View {
ZStack{
VStack {
Rectangle()
.fill(Color.green)
.frame(height: 300)
Spacer()
ScrollView {
VStack{
Rectangle()
.fill(Color.red)
.frame(height: 300)
Rectangle()
.fill(Color.yellow)
.frame(height: 300)
Button(action: /*#START_MENU_TOKEN#*/{}/*#END_MENU_TOKEN#*/) {
Text("Click me")
}.padding() //instead of offset
.frame(width: 300)
}.frame(alignment: .leading)
}
}.edgesIgnoringSafeArea(.all)
}
}
Have you tried moving the Button out of the ScrollView()?
Here is what looks like:
struct ContentView: View {
var body: some View {
VStack {
Rectangle()
.fill(Color.green)
.frame(height: 300)
Spacer()
ScrollView(.vertical) {
Rectangle().fill(Color.red).frame(width: 400, height: 200)
Rectangle().fill(Color.blue).frame(width: 400, height: 200)
Rectangle().fill(Color.yellow).frame(width: 400, height: 200)
Rectangle().fill(Color.green).frame(width: 400, height: 200)
}
Button(action: /*#START_MENU_TOKEN#*/{}/*#END_MENU_TOKEN#*/) {
Text("Click me")
.font(.headline)
}
}.edgesIgnoringSafeArea(.all)
}
}

Unable to change background color of view in SwiftUI

I am trying to change background color main this view but unable to do it. I tried to put background(Color.green) at HStack, VSTack and even on ZStack but it did not work, not sure if i am putting at right place. By default it is taking phone or simulator color which is white but i want to apply custom background color
My Xcode version is 11.5
struct HomePageView: View {
#State var size = UIScreen.main.bounds.width / 1.6
var body: some View {
GeometryReader{_ in
VStack {
HStack {
ZStack{
// main home page components here....
NavigationView{
VStack {
AssignmentDaysView()
}.background(Color.lairBackgroundGray)
.frame(width: 100, height: 100, alignment: .top)
.navigationBarItems(leading: Button(action: {
self.size = 10
}, label: {
Image("menu")
.resizable()
.frame(width: 30, height: 30)
}).foregroundColor(.appHeadingColor), trailing:
Button(action: {
print("profile is pressed")
}) {
HStack {
NavigationLink(destination: ProfileView()) {
LinearGradient.lairHorizontalDark
.frame(width: 30, height: 30)
.mask(
Image(systemName: "person.crop.circle")
.resizable()
.scaledToFit()
)
}
}
}
).navigationBarTitle("Home", displayMode: .inline)
}
HStack{
menu(size: self.$size)
.cornerRadius(20)
.padding(.leading, -self.size)
.offset(x: -self.size)
Spacer()
}
Spacer()
}.animation(.spring()).background(Color.lairBackgroundGray)
Spacer()
}
}
}
}
}
struct HomePageView_Previews: PreviewProvider {
static var previews: some View {
HomePageView()
}
}
In your NavigationView you have a VStack. Instead you can use a ZStack and add a background below your VStack.
Try the following:
NavigationView {
ZStack {
Color.green // <- or any other Color/Gradient/View you want
.edgesIgnoringSafeArea(.all) // <- optionally, if you want to cover the whole screen
VStack {
Text("assignments")
}
.background(Color.gray)
.frame(width: 100, height: 100, alignment: .top)
}
}
Note: you use many stacks wrapped in a GeometryReader which you don't use. Consider simplifying your View by removing unnecessary stacks. Also you may not need a GeometryReader if you use UIScreen.main.bounds (however, GeometryReader is preferred in SwiftUI).
Try removing some layers: you can start with removing the top ones: GeometryReader, VStack, HStack...
Try the following:
Change the view background color especially safe area also
struct SignUpView: View {
var body: some View {
ZStack {
Color.blue //background color
}.edgesIgnoringSafeArea(.all)

Center View horizontally in SwiftUI

How can I center horizontally a View (Image) in an HStack? I want a button to be left aligned and the image to be centered horizontally the view.
Currently I have this structure:
VStack {
HStack {
Button(action: {
print("Tapped")
}, label: {
Image("left-arrow")
.resizable()
.frame(width: 30, height: 30, alignment: .leading)
}).padding(.leading, 20)
Spacer()
Image("twitter-logo")
.resizable()
.frame(width: 30, height: 30, alignment: .center)
}
Spacer()
}
Which is giving me this:
But I want to achieve this:
You can embed two HStack's in a ZStack and place spacers accordingly for the horizontal spacing. Embed all that in a VStack with a Spacer() to have everything pushed up to the top.
struct ContentView : View {
var buttonSize: Length = 30
var body: some View {
VStack {
ZStack {
HStack {
Button(action: {
}, label: {
Image(systemName: "star")
.resizable()
.frame(width: CGFloat(30), height: CGFloat(30), alignment: .leading)
}).padding(.leading, CGFloat(20))
Spacer()
}
HStack {
Image(systemName: "star")
.resizable()
.frame(width: CGFloat(30), height: CGFloat(30), alignment: .center)
}
}
Spacer()
}
}
}
Note: In the second HStack, the image should automatically be center aligned, but if it isn't, you can place a Spacer() before and after the image.
Edit: Added the VStack and Spacer() to move everything to the top like the OP wanted.
Edit 2: Removed padding on image because it caused the image to be slightly offset from the center. Since it is in its own HStack and center-aligned, it does not need padding.
Edit 3: Thanks to #Chris Prince in the comments, I decided to make a simple NavigationBar-esque custom view that you can provide left, center, and right arguments to create the effect that the OP desired (where each set of views are aligned independently of each other):
struct CustomNavBar<Left, Center, Right>: View where Left: View, Center: View, Right: View {
let left: () -> Left
let center: () -> Center
let right: () -> Right
init(#ViewBuilder left: #escaping () -> Left, #ViewBuilder center: #escaping () -> Center, #ViewBuilder right: #escaping () -> Right) {
self.left = left
self.center = center
self.right = right
}
var body: some View {
ZStack {
HStack {
left()
Spacer()
}
center()
HStack {
Spacer()
right()
}
}
}
}
Usage:
struct ContentView: View {
let buttonSize: CGFloat = 30
var body: some View {
VStack {
CustomNavBar(left: {
Button(action: {
print("Tapped")
}, label: {
Image(systemName: "star")
.resizable()
.frame(width: self.buttonSize, height: self.buttonSize, alignment: .leading)
}).padding()
}, center: {
Image(systemName: "star")
.resizable()
.frame(width: 30, height: 30, alignment: .center)
}, right: {
HStack {
Text("Long text here")
Image(systemName: "star")
.resizable()
.frame(width: 30, height: 30, alignment: .center)
.padding(.trailing)
}.foregroundColor(.red)
})
Spacer()
Text("Normal Content")
Spacer()
}
}
}
What's about saving button size to a property and add a negative padding to the image? And pay attention to an additional spacer after the image.
struct ContentView: View {
var buttonSize: Length = 30
var body: some View {
VStack {
HStack {
Button(action: {
print("Tapped")
}, label: {
Image(systemName: "star")
.resizable()
.frame(width: buttonSize, height: buttonSize, alignment: .leading)
})
Spacer()
Image(systemName: "star")
.resizable()
.frame(width: 30, height: 30, alignment: .center)
.padding(.leading, -buttonSize)
Spacer()
}
Spacer()
}
}
}
The result:
Easiest way for me:
ZStack(){
HStack{
Image("star").resizable().foregroundColor(.white).frame(width: 50, height: 50)
Spacer()
}
Image("star").resizable().font(.title).foregroundColor(.white).frame(width: 50, height: 50)
}
You center the view using position property try this code
Group{ // container View
Image("twitter-logo")
.resizable()
.frame(width: 30, height: 30, alignment: .center)
}.position(x: UIScreen.main.bounds.width/2)
the right way to center the Title like navigationbar:
HStack {
Spacer()
.overlay {
HStack {
Image(systemName: "star")
Spacer()
}
}
Text("Title")
Spacer()
.overlay {
HStack {
Spacer()
Image(systemName: "star")
}
}
}
You can place the view that you want to center into a VStack and then set the alignment to center. Make sure that you also set the frame(maxWidth: .infinity) or else it will be centering your view in the VStack but the VStack might not take up the entire width of the screen so you might not get the appearance you are trying to achieve.
To make it even easier, write it as a function that extends the View object
extension View {
func centerInParentView() -> some View {
VStack(alignment: .center) {
self
}
.frame(maxWidth: .infinity)
}
}
And then you can just call it as you would a view modifier i.e.
VStack {
HStack {
Button(action: {
print("Tapped")
}, label: {
Image("left-arrow")
.resizable()
.frame(width: 30, height: 30, alignment: .leading)
}).padding(.leading, 20)
Spacer()
Image("twitter-logo")
.resizable()
.frame(width: 30, height: 30, alignment: .center)
}
Spacer()
}
.centerInParentView()
Works every time for me
I have got an alternative solution. I used a hidden Image as placeholder.
HStack {
Image("left-arrow").padding()
Spacer()
Image("twitter-logo")
Spacer()
// placeholder to keep layout symmetric
Image("left-arrow").padding().hidden()
}
Of course you can replace the Images with Buttons or other Views as you prefer.
Here is what worked for me
HStack {
Image(systemName: "star.fill")
.frame(maxWidth: .infinity, alignment: .leading)
Image(systemName: "star.fill")
.frame(maxWidth: .infinity, alignment: .center)
Text("")
.frame(maxWidth: .infinity, alignment: .trailing)
}
.foregroundColor(.yellow)
Inspired by SwiftUI - How to align elements in left, center, and right within HStack?
Let me propose a different solution:
https://gist.github.com/buscarini/122516641cd0ee275dd367786ff2a736
It can be used like this:
HStack {
Color.red
.frame(width: 0, height: 50)
.layoutPriority(1)
GlobalHCenteringView {
Text("Hello, world!")
.lineLimit(1)
.background(Color.green)
}
.background(Color.yellow)
Color.red
.frame(width: 180, height: 50)
.layoutPriority(1)
}
}
This will center the child view in the screen if it fits, or leave it as is if it doesn't. It is currently using UIScreen, so it only works on iOS, but you could easily pass the screen or parent width to the constructor of the view, getting it from a GeometryReader or whatever.

Resources