SwiftUI ZStack takes all screen height but should be fixed height - ios

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)

Related

SwiftUI simple view, need a push into right direction

I am complete beginner with SwiftUI and I can't wrap my head around how to connect these images with views that represents lines. Now I simply have 3 VStacks with image and text and put them into a HStack, but don't know how to connect these images with a line shown in red in the picture I attached. Note that there's some space between the line and the image. I need general direction and some hints, full working code not necessary.
Thank you.
How's this?
In SwiftUI, you use HStacks and VStacks to stack your Views. For the red line, a Rectangle should do. Here's the code:
struct ContentView: View {
var body: some View {
HStack { /// horizontal stack
VStack {
Image(systemName: "face.smiling")
.font(.system(size: 80))
.padding()
.border(Color.black, width: 5)
Text("Text TEXTEXT")
}
Rectangle()
.fill(Color.red)
.frame(height: 5)
VStack {
Image(systemName: "face.smiling")
.font(.system(size: 80))
.padding()
.border(Color.black, width: 5)
Text("Text TEXTEXT")
}
Rectangle()
.fill(Color.red)
.frame(height: 5)
VStack {
Image(systemName: "face.smiling")
.font(.system(size: 80))
.padding()
.border(Color.black, width: 5)
Text("Text TEXTEXT")
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
.previewLayout(.fixed(width: 800, height: 200))
}
}
Version 1.0.0
I decided to give my answer which is same like aheze answer with this difference that you can have CustomVerticalAlignment as well! As I see in your Image in question you want that also:
with CustomVerticalAlignment: In center!
without CustomVerticalAlignment: off center!
import SwiftUI
struct ContentView: View {
var body: some View {
HStack(alignment: .customVerticalAlignment) {
VStack {
Image(systemName: "star")
.resizable()
.scaledToFit()
.frame(width: 50, height: 50, alignment: .center)
.padding()
.border(Color.black, width: 5)
.alignmentGuide(.customVerticalAlignment) { d in d[VerticalAlignment.center] }
Text("Text")
}
Capsule()
.fill(Color.red)
.frame(height: 5)
.alignmentGuide(.customVerticalAlignment) { d in d[VerticalAlignment.center] }
VStack {
Image(systemName: "star")
.resizable()
.scaledToFit()
.frame(width: 50, height: 50, alignment: .center)
.padding()
.border(Color.black, width: 5)
.alignmentGuide(.customVerticalAlignment) { d in d[VerticalAlignment.center] }
Text("Text")
}
Capsule()
.fill(Color.red)
.frame(height: 5)
.alignmentGuide(.customVerticalAlignment) { d in d[VerticalAlignment.center] }
VStack {
Image(systemName: "star")
.resizable()
.scaledToFit()
.frame(width: 50, height: 50, alignment: .center)
.padding()
.border(Color.black, width: 5)
.alignmentGuide(.customVerticalAlignment) { d in d[VerticalAlignment.center] }
Text("Text")
}
}
.padding()
}
}
extension VerticalAlignment {
struct CustomVerticalAlignment: AlignmentID {
static func defaultValue(in d: ViewDimensions) -> CGFloat {
d[VerticalAlignment.center]
}
}
static let customVerticalAlignment = VerticalAlignment(CustomVerticalAlignment.self)
}
Update Version 2.0.0
About this version: I would say it does the same job of version 1.0.0 in less code and also Text and Line are not depending on VStack or eachother any moere!
import SwiftUI
struct ContentView: View {
var body: some View {
HStack {
image.overlay(text.offset(y: 40), alignment: .bottom)
capsule
image.overlay(text.offset(y: 40), alignment: .bottom)
capsule
image.overlay(text.offset(y: 40), alignment: .bottom)
}
.padding(50)
}
var image: some View {
return Image(systemName: "star.fill")
.resizable()
.scaledToFit()
.padding(10)
.shadow(radius: 10)
.frame(width: 50, height: 50, alignment: .center)
.foregroundColor(Color.red)
.background(Color.yellow)
.border(Color.black, width: 5)
}
var capsule: some View {
return Capsule()
.fill(Color.red)
.frame(height: 5)
}
var text: some View {
return Text("Hello World!")
.lineLimit(1)
.fixedSize()
}
}
You could define a Shape that represents your line.
I used the spacing parameter of HStack to do the spacing:
struct MyLine : Shape {
func path(in rect: CGRect) -> Path {
Path { path in
path.move(to: CGPoint(x: 0, y: rect.midY))
path.addLine(to: CGPoint(x: rect.maxX, y: rect.midY))
}
}
}
struct ContentView: View {
var body: some View {
HStack(spacing: 10) {
VStack {
Image(systemName: "pencil")
Text("Label")
}
MyLine().stroke(Color.red)
VStack {
Image(systemName: "pencil")
Text("Label 2")
}
MyLine().stroke(Color.red)
VStack {
Image(systemName: "pencil")
Text("Label 3")
}
}
}
}
You could add a lineWidth parameter to make the stroke thicker:
.stroke(Color.red, lineWidth: 4)
Also, if you didn't using spacing on the HStack, you could using a padding modifier on either the VStacks or the MyLines to get the spacing.

View centered in superview with view on top of it in SwiftUI

I'm trying to achieve something that is quite easy in UIKit - one view that is always in in the center (image) and the second view (text) is on top of it with some spacing between two views. I tried many different approaches (mainly using alignmentGuide but nothing worked as I'd like).
code:
ZStack {
Rectangle()
.foregroundColor(Color.red)
VStack {
Text("Test")
.padding([.bottom], 20) // I want to define spacing between two views
Image(systemName: "circle")
.resizable()
.alignmentGuide(VerticalAlignment.center, computeValue: { value in
value[VerticalAlignment.center] + value.height
})
.frame(width: 20, height: 20)
}
}
.frame(width: 100, height: 100)
result:
As you can see image is not perfectly centered and it actually depends on the padding value of the Text. Is there any way to force vertical and horizontal alignment to be centered in the superview and layout second view without affecting centered view?
I think the “correct” way to do this is to define a custom alignment:
extension VerticalAlignment {
static var custom: VerticalAlignment {
struct CustomAlignment: AlignmentID {
static func defaultValue(in context: ViewDimensions) -> CGFloat {
context[VerticalAlignment.center]
}
}
return .init(CustomAlignment.self)
}
}
Then, tell your ZStack to use the custom alignment, and use alignmentGuide to explicitly set the custom alignment on your circle:
PlaygroundPage.current.setLiveView(
ZStack(alignment: .init(horizontal: .center, vertical: .custom)) {
Color.white
Rectangle()
.fill(Color.red)
.frame(width: 100, height: 100)
VStack {
Text("Test")
Circle()
.stroke(Color.white)
.frame(width: 20, height: 20)
.alignmentGuide(.custom, computeValue: { $0.height / 2 })
}
}
.frame(width: 300, height: 300)
)
Result:
You can center the Image by moving it to ZStack. Then apply .alignmentGuide to the Text:
struct ContentView: View {
var body: some View {
ZStack {
Rectangle()
.foregroundColor(Color.red)
Text("Test")
.alignmentGuide(VerticalAlignment.center) { $0[.bottom] + $0.height }
Image(systemName: "circle")
.resizable()
.frame(width: 20, height: 20)
}
.frame(width: 100, height: 100)
}
}
Note that as you specify the width/height of the Image explicitly:
Image(systemName: "circle")
.resizable()
.frame(width: 20, height: 20)
you can specify the .alignmentGuide explicitly as well:
.alignmentGuide(VerticalAlignment.center) { $0[.bottom] + 50 }
Here is possible alternate, using automatic space consuming feature
Tested with Xcode 12 / iOS 14
struct ContentView: View {
var body: some View {
ZStack {
Rectangle()
.foregroundColor(Color.red)
VStack(spacing: 0) {
Color.clear
.overlay(
Text("Test").padding([.bottom], 10),
alignment: .bottom)
Image(systemName: "circle")
.resizable()
.frame(width: 20, height: 20)
Color.clear
}
}
.frame(width: 100, height: 100)
}
}
Note: before I used Spacer() for such purpose but with Swift 2.0 it appears spacer becomes always just a spacer, ie. nothing can be attached to it - maybe bug.

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)
}
}

Home page view background color in not on full screen in iPhone 11 Simulator

I am trying to build an app in SwiftUI and facing 1 challenge (Xcode Version 11.5) -
While running app on iPhone 11 simulator, background color is not coming on entire screen, bottom part of screen is still white however while running it on iPhone 8 simulator, it works fine. Not sure if it is simulator issue or code issue. I tried to add Spacer, change VStack, HStack but it did not work.
struct HomePageView: View {
#State var size = UIScreen.main.bounds.width / 1.6
var body: some View {
GeometryReader{geometry in
VStack {
HStack {
ZStack{
NavigationView{
ZStack {
ScrollView(.vertical, showsIndicators: false) {
VStack {
View1()
}.frame( maxWidth: .infinity)
}
.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().background(Color.lairBackgroundGray)
}
//Spacer()
}.animation(.spring()).background(Color.lairBackgroundGray)
//Spacer()
}.padding(.top, UIApplication.shared.windows.first?.safeAreaInsets.top)
.padding(.bottom, UIApplication.shared.windows.first?.safeAreaInsets.bottom)
}.frame(height: geometry.size.height).background(Color.lairBackgroundGray)
}//.background(Color.lairBackgroundGray.edgesIgnoringSafeArea(.all))
}
}
Below is my another view which basically get painted on screen as part of home view. Please forgive me to put so much code here but wanted to make sure if it is not because of View1 -
struct View1: View {
#State var index = 0
var body: some View{
// ScrollView {
GeometryReader { geometry in
VStack{
HStack{
VStack {
ZStack{
Circle()
.trim(from: 0, to: 1)
.stroke(Color.lairDarkGray.opacity(0.09), style: StrokeStyle(lineWidth: 34, lineCap: .round))
.frame(width: 80, height: 80)
Circle()
.trim(from: 0, to: 0.5)
.stroke(LinearGradient(gradient: Gradient(colors: [.buttonGradientStartColor, .buttonGradientEndColor]), startPoint: UnitPoint(x: -0.2, y: 0.5), endPoint: .bottomTrailing), style: StrokeStyle(lineWidth: 34, lineCap: .round))
.frame(width: 80 , height: 80)
.rotationEffect(.init(degrees: -90))
Text("15")
.font(.system(size:30))
.fontWeight(.bold)
}.padding()
Text("Day(s)")
.foregroundColor(Color.black.opacity(0.8))
}.frame(height: 100)
VStack(alignment: .leading, spacing: 12){
HStack {
Image("1")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 170, height: 170)
}
.background(Color.lairBackgroundGray)
//.padding(.bottom, 5)
}
.padding(.leading, 20)
Spacer(minLength: 0)
}
.padding(.horizontal, 20)
}//.frame(height: geometry.size.height)
.background(Color.lairBackgroundGray.edgesIgnoringSafeArea(.all))
}
//}
}
}[![enter image description here][1]][1]
You just need to add
.edgesIgnoringSafeArea(.all)
to the view that you want to go fullscreen
After recreating entire view and removing some unnecessary view, i was able to resolve this iissue.

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