How to: Layout in Swift UI using ZStack, VStack, HStack - ios

I am trying to achieve this layout https://gyazo.com/9714f8f1bff98edb3365338563b28fe8 in Swift UI using V and H Stacks. So far I have achieved this https://gyazo.com/fec9ae229e59a41add6542c9a8ad31af. I haven't found the right approach just yet on what to do here for more manipulation. What properties would help me achieve the desired layout?
Update! Here is what I have figured out so far... Almost there.
UPDATED CODE AND CANVAS HERE: https://gyazo.com/d12b9a831f50c25f8f854dc2143c93dc
import SwiftUI
struct CustomBlock: View {
var leading: String
var trailing: String
var body: some View {
VStack {
HStack (alignment: .firstTextBaseline, spacing: 18){
Button(action: {}){
Text(leading)
.bold()
.font(Font.custom("Helvetica Neue", size: 21.0))
.padding(25)
.foregroundColor(Color.white)
.background(Color.green)
.cornerRadius(12)
.multilineTextAlignment(.center)
.fixedSize()
.frame(width: 150, height: 50, alignment: .center)
}
Button(action: {}){
Text(trailing)
.bold()
.font(Font.custom("Helvetica Neue", size: 21.0))
.padding(25)
.foregroundColor(Color.white)
.background(Color.green)
.cornerRadius(12)
.multilineTextAlignment(.center)
.fixedSize()
.frame(width: 150, height: 50, alignment: .center)
}
}
}.padding()
}
}
struct ContentView: View {
var body: some View {
ZStack {
Color.init(hue: 0.2722, saturation: 0.89, brightness: 0.29, opacity: 1.0) .edgesIgnoringSafeArea(.all)
VStack {
HStack {
StrokeText(text: "Dividend Chaser", width: 0.5, color: .black)
.foregroundColor(.white)
.font(.system(size: 45, weight: .bold))
}
ZStack {
RoundedRectangle(cornerRadius: 25, style: .continuous)
.fill( Color.init(red: 0.33, green: 0.56, blue: 0.27))
.frame(width: 350, height: 240)
}
CustomBlock(leading: "Today's List", trailing: "Tomorrow's List").lineLimit(2)
CustomBlock(leading: "This Month", trailing: "Next Month").lineLimit(2)
CustomBlock(leading: "3% Yeild Or Higher", trailing: "5% Yeild Or Higher").lineLimit(2)
CustomBlock(leading: "App Help", trailing: "More Apps").lineLimit(2)
}
}
}
}

Here is a demo of approach. Tested with Xcode 12 / iOS 14.
struct DemoButtonStyle: ButtonStyle {
func makeBody(configuration: Configuration) -> some View {
configuration.label
.foregroundColor(.white).font(Font.body.bold())
.frame(maxWidth: .infinity).padding(.vertical, 10)
.background(RoundedRectangle(cornerRadius: 8).fill(Color.green))
.multilineTextAlignment(.center)
.frame(maxWidth: .infinity)
}
}
struct TestButtonsGridLayout: View {
var body: some View {
VStack(spacing: 1) {
HStack(spacing: 1) {
Button("Today's\nList") {}
Button("Tomorrow's\nList") {}
}
HStack(spacing: 1) {
Button("This\nMonth") {}
.overlay(RoundedRectangle(cornerRadius: 8)
.stroke(lineWidth: 4).foregroundColor(.white).padding(2))
Button("Next\nMonth") {}
}
// .. other same here
}.buttonStyle(DemoButtonStyle())
}
}
Note: a) button style can be applied per-button b) show overlay selection can be also moved in style and activated by some state variable.

Related

TextField stroke border text with SwiftUI

Here is what I've done, but the problem is with Text background. It can be implemented on white background by setting the Text's background to white as well, but in case of image background it stays "strikedthrough". You can find a source code below where I tried to make it as close to the result as possible. How it could be resolved?
struct CustomTextField: View {
let placeholder: String
#Binding var text: String
var body: some View {
TextField("", text: $text)
.placeholder(when: $text.wrappedValue.isEmpty,
alignment: .leading,
placeholder: {
Text(placeholder)
.foregroundColor(.gray)
.font(.system(size: 20))
.padding(.leading, 15)
})
.foregroundColor(.gray)
.font(.system(size: 20))
.padding(EdgeInsets(top: 15, leading: 10, bottom: 15, trailing: 10))
.background {
ZStack {
RoundedRectangle(cornerRadius: 5)
.stroke(.gray, lineWidth: 1)
Text(placeholder)
.foregroundColor(.gray)
.padding(2)
.font(.caption)
.frame(maxWidth: .infinity,
maxHeight: .infinity,
alignment: .topLeading)
.offset(x: 20, y: -10)
}
}
}
}
Here is a solution using .trim on two RoundedRectangles based on the length of the label text, which should give you the result you want:
struct CustomTextField: View {
let placeholder: String
#Binding var text: String
#State private var width = CGFloat.zero
#State private var labelWidth = CGFloat.zero
var body: some View {
TextField(placeholder, text: $text)
.foregroundColor(.gray)
.font(.system(size: 20))
.padding(EdgeInsets(top: 15, leading: 10, bottom: 15, trailing: 10))
.background {
ZStack {
RoundedRectangle(cornerRadius: 5)
.trim(from: 0, to: 0.55)
.stroke(.gray, lineWidth: 1)
RoundedRectangle(cornerRadius: 5)
.trim(from: 0.565 + (0.44 * (labelWidth / width)), to: 1)
.stroke(.gray, lineWidth: 1)
Text(placeholder)
.foregroundColor(.gray)
.overlay( GeometryReader { geo in Color.clear.onAppear { labelWidth = geo.size.width }})
.padding(2)
.font(.caption)
.frame(maxWidth: .infinity,
maxHeight: .infinity,
alignment: .topLeading)
.offset(x: 20, y: -10)
}
}
.overlay( GeometryReader { geo in Color.clear.onAppear { width = geo.size.width }})
.onChange(of: width) { _ in
print("Width: ", width)
}
.onChange(of: labelWidth) { _ in
print("labelWidth: ", labelWidth)
}
}
}
Here is my version of the TextField.
struct TextInputField: View {
let placeHolder: String
#Binding var textValue: String
var body: some View {
ZStack(alignment: .leading) {
Text(placeHolder)
.foregroundColor(Color(.placeholderText))
.offset(y: textValue.isEmpty ? 0 : -25)
.scaleEffect(textValue.isEmpty ? 1: 0.8, anchor: .leading)
TextField("", text: $textValue)
}
.padding(.top, textValue.isEmpty ? 0 : 15)
.frame(height: 52)
.padding(.horizontal, 16)
.overlay(RoundedRectangle(cornerRadius: 12).stroke(lineWidth: 1).foregroundColor(.gray))
.animation(.default)
}
}
The above code is to create a CustomTextField named TextInputField. If you want to use the about component
struct ContentView: View {
#State var itemName: String = ""
var body: some View {
TextInputField(placeHolder: "Item Name": textValue: $itemName)
}
}
I'm using #ChrisR's answer as a base for my answer, so instead of doing all that calculation with two RoundedRectangles and label's width; you can position the Text on top and give it a background matching the app's background color
struct FloatingTitleTextField: View {
let placeholder: String
#Binding var text: String
var body: some View {
TextField("Placeholder", text: $text)
.foregroundColor(.gray)
.font(.system(size: 20))
.padding(EdgeInsets(top: 15, leading: 10, bottom: 15, trailing: 10))
.background {
ZStack {
RoundedRectangle(cornerRadius: 5)
.stroke(.black, lineWidth: 1)
Text(placeholder)
.foregroundColor(.gray)
.padding(2)
.background()
.frame(maxWidth: .infinity,
maxHeight: .infinity,
alignment: .topLeading)
.offset(x: 20, y: -10)
}
}
}
}
When calling the textfield you do it like this
FloatingTitleTextField(placeholder: "Placeholder", text: $text)
I also found this article very helpful

SwiftUI View is taking exceeding edges

I am learning SwiftUI and was trying to replicate an app. I ran into a problem where the view is taking up space outside the frame as well.
It looks like this:
My code for the view is:
struct LessonsScreen: View {
var body: some View {
VStack (alignment: .leading) {
HStack {
Image(systemName: "arrow.left")
.font(.system(size: 30))
Spacer()
Image(systemName: "slider.horizontal.3")
.font(.system(size: 30))
}
Text("A2 - Elementary")
.font(.system(size: 28, weight: .semibold))
.padding()
LessonCompletion(lessonNum: 1, text: "How are you?", color: .purple)
Image("discussion")
.cornerRadius(20)
.frame(width: UIScreen.main.bounds.width, alignment: .center)
LessonCompletion(lessonNum: 2, text: "Pronunciation", color: .green)
LessonCompletion(lessonNum: 3, text: "Demonstrative pronouns", color: .red)
LessonCompletion(lessonNum: 4, text: "Present continuous", color: .yellow)
Button(action: {}, label: {
Text("Get started")
.foregroundColor(.white)
.frame(width: UIScreen.main.bounds.width - 150, height: 70, alignment: .center)
.background(Color.black)
.cornerRadius(10)
.frame(width: UIScreen.main.bounds.width, alignment: .center)
.padding()
})
}
}
}
Can anyone tell me where I messed up the formatting?
If you like to align the button in center in native SwiftUI way, you can use view modifier Spacer() inside HStack() instead of .frame, like this: (and same with 'discussion' Image)
...
Button(action: {}, label: {
HStack{
Spacer()
Text("Get started")
.foregroundColor(.white)
.frame(width: UIScreen.main.bounds.width - 150, height: 70, alignment: .center)
.background(Color.black)
.cornerRadius(10)
.padding()
Spacer()
}
})
...
In your button replace
.frame(width: UIScreen.main.bounds.width, alignment: .center)
with
.frame(maxWidth: .infinity, alignment: .center)

Button overlay is showing over Button Text, SwiftUI

My Button requires a couple overlays to get the style it needs (stroke, shadow etc).
However these overlays then go over the Button text.
View:
struct ProfileTest: View {
var body: some View {
Button(action: {
}){
HStack {
Text("OFFLINE")
.font(.custom("Seravek-Bold", size: 25))
.fontWeight(.bold)
.foregroundColor(Color.blue)
}
}.padding(EdgeInsets(top: 12, leading: 15, bottom: 12, trailing: 15))
.cornerRadius(50)
.overlay(
RoundedRectangle(cornerRadius: 50)
.stroke(Color.black, lineWidth: 1)
)
.overlay(
RoundedRectangle(cornerRadius: 50)
.fill(Color.red)
.shadow(color: Color.black.opacity(1), radius: 1)
)
.opacity(0.7)
.padding(.bottom, 100)
.padding(.bottom, 20)
}
}
How can I adjust my view so that the blue text shows above the background?
You can use a custom ButtonStyle.
Create your own shape and add .overlay(configuration.label) on top of it:
struct CustomButtonStyle: ButtonStyle {
func makeBody(configuration: Self.Configuration) -> some View {
configuration.label
.hidden()
.padding(EdgeInsets(top: 12, leading: 15, bottom: 12, trailing: 15))
.cornerRadius(50)
.overlay(
RoundedRectangle(cornerRadius: 50)
.stroke(Color.black, lineWidth: 1)
)
.overlay(
RoundedRectangle(cornerRadius: 50)
.fill(Color.red)
.shadow(color: Color.black.opacity(1), radius: 1)
)
.opacity(0.7)
.overlay(configuration.label)
.padding(.bottom, 100)
.padding(.bottom, 20)
}
}
You can also use configuration.isPressed to customise your label behaviour on button press.
Apply it to your Button:
struct ContentView: View {
var body: some View {
Button(action: {}) {
HStack {
Text("OFFLINE")
.font(.custom("Seravek-Bold", size: 25))
.fontWeight(.bold)
.foregroundColor(Color.blue)
}
}
.buttonStyle(CustomButtonStyle())
}
}

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.

How to : Format Swift UI Buttons

I am figuring out Swift UI one step at a time. Here I have a VStack with a nested HStack setup holding some Buttons. I need to format them so they look a little less jumbled together.. what is the correct property to use? Is there a property I can use for the whole HStack view to handle this formatting automatically?
Simulator: https://gyazo.com/d566bc3ba8ea4de446f346d7098c1424
import SwiftUI
struct ContentView: View {
var body: some View {
ZStack {
Color.init(hue: 0.2722, saturation: 0.89, brightness: 0.29, opacity: 1.0) .edgesIgnoringSafeArea(.all)
VStack {
HStack {
Button(action: {}){
Text("Today's List")
.bold()
.font(Font.custom("Helvetica Neue", size: 21.0))
.padding(18)
.foregroundColor(Color.white)
.background(Color.green)
.cornerRadius(12)
}
Button(action: {}){
Text("Tomorrow's List")
.bold()
.font(Font.custom("Helvetica Neue", size: 21.0))
.padding(18)
.foregroundColor(Color.white)
.background(Color.green)
.cornerRadius(12)
}
}
VStack {
HStack {
Button(action: {}){
Text("This Month")
.bold()
.font(Font.custom("Helvetica Neue", size: 21.0))
.padding(18)
.foregroundColor(Color.white)
.background(Color.green)
.cornerRadius(12)
}
Button(action: {}){
Text("Next Month")
.bold()
.font(Font.custom("Helvetica Neue", size: 21.0))
.padding(18)
.foregroundColor(Color.white)
.background(Color.green)
.cornerRadius(12)
}
}
}
VStack {
HStack {
Button(action: {}){
Text("3% Yeild Or Higher")
.bold()
.font(Font.custom("Helvetica Neue", size: 21.0))
.padding(18)
.foregroundColor(Color.white)
.background(Color.green)
.cornerRadius(12)
}
Button(action: {}){
Text("5% Yeild Or Higher")
.bold()
.font(Font.custom("Helvetica Neue", size: 21.0))
.padding(18)
.foregroundColor(Color.white)
.background(Color.green)
.cornerRadius(12)
}
}
}
VStack {
HStack {
Button(action: {}){
Text("App Help")
.bold()
.font(Font.custom("Helvetica Neue", size: 21.0))
.padding(18)
.foregroundColor(Color.white)
.background(Color.green)
.cornerRadius(12)
}
Button(action: {}){
Text("More Apps")
.bold()
.font(Font.custom("Helvetica Neue", size: 21.0))
.padding(18)
.foregroundColor(Color.white)
.background(Color.green)
.cornerRadius(12)
}
}
}
}
}
}
}
//Header
//Data
//Buttons
struct StrokeText: View {
let text: String
let width: CGFloat
let color: Color
var body: some View {
ZStack{
ZStack{
Text(text).offset(x: width, y: width)
Text(text).offset(x: -width, y: -width)
Text(text).offset(x: -width, y: width)
Text(text).offset(x: width, y: -width)
}
.foregroundColor(color)
Text(text)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
EDIT: What can I do to make the buttons take this shape? Formatted into more of a box layout.
image for reference: https://gyazo.com/e142e7358083987f3ebde48b66841f52
First off, you may want to refactor your code, clean it up. Say add a method that returns some View to generate Text objects for you. I haven't used SwiftUI much so it was interesting to me to go through your code for a couple of minutes.
import SwiftUI
struct ContentView: View {
var body: some View {
ZStack {
Color.init(hue: 0.2722, saturation: 0.89, brightness: 0.29, opacity: 1.0) .edgesIgnoringSafeArea(.all)
VStack(alignment: .center, spacing: 8) {
HStack(alignment: .firstTextBaseline, spacing: 8) {
Button(action: {}){
self.newText("Today's List")
}
Button(action: {}){
self.newText("Tomorrow's List")
}
}
HStack(alignment: .firstTextBaseline, spacing: 8) {
Button(action: {}){
self.newText("This Month")
}
Button(action: {}){
self.newText("Next Month")
}
}
HStack(alignment: .firstTextBaseline, spacing: 8) {
Button(action: {}){
self.newText("% Yeild Or Higher")
}
Button(action: {}){
self.newText("5% Yeild Or Higher")
}
}
HStack(alignment: .firstTextBaseline, spacing: 8) {
Button(action: {}){
self.newText("App Help")
}
Button(action: {}){
self.newText("More Apps")
}
}
}
}
}
func newText(_ text: String) -> some View {
let text = Text(text)
.bold()
.font(Font.custom("Helvetica Neue", size: 21.0))
.padding(18)
.foregroundColor(Color.white)
.background(Color.green)
.cornerRadius(12)
return text
}
}
As you can see, you can remove the unnecessary VStacks, make a "Text factory" method that returns Text objects, utilize the stacks' alignment and spacing.
And here's your output:
Edit: You wanted to distribute the buttons equally? I took time to experiment, tinkering the views' frames.
You need to set the button's bg color instead of the text. And with that, you can refactor further your code by making a button factory method that returns some View.
struct ContentView: View {
var body: some View {
ZStack {
Color.init(hue: 0.2722, saturation: 0.89, brightness: 0.29, opacity: 1.0) .edgesIgnoringSafeArea(.all)
VStack(alignment: .center, spacing: 4) {
HStack(alignment: .center, spacing: 4) {
self.newButton(text: "Today's List") { }
self.newButton(text: "Tomorrow's List") { }
}
HStack(alignment: .firstTextBaseline, spacing: 4) {
self.newButton(text: "This Month") { }
self.newButton(text: "Next Month") { }
}
HStack(alignment: .firstTextBaseline, spacing: 4) {
self.newButton(text: "% Yeild Or Higher") { }
self.newButton(text: "5% Yeild Or Higher") { }
}
HStack(alignment: .firstTextBaseline, spacing: 4) {
self.newButton(text: "App Help") { }
self.newButton(text: "More Apps") { }
}
}
}
}
func newButton(text: String, action: #escaping () -> Void) -> some View {
let button = Button(action: action){
self.newText(text)
}.background(Color.green)
.cornerRadius(12)
.frame(minWidth: 0,
maxWidth: .infinity,
minHeight: 0,
maxHeight: 100.0)
return button
}
func newText(_ text: String) -> some View {
let text = Text(text)
.bold()
.font(Font.custom("Helvetica Neue", size: 21.0))
.padding(8)
.foregroundColor(Color.white)
.frame(maxWidth: .infinity, maxHeight: 100.0)
return text
}
}
And here's your new output:
For a start, you can add padding to the VStacks. And do a bit of refactoring for VStack.
struct ContentView: View {
var body: some View {
ZStack {
Color.init(hue: 0.2722, saturation: 0.89, brightness: 0.29, opacity: 1.0) .edgesIgnoringSafeArea(.all)
VStack {
CustomBlock(leading: "Today's List", trailing: "Tomorrow's List")
CustomBlock(leading: "This Month", trailing: "Next Month")
CustomBlock(leading: "3% Yeild Or Higher", trailing: "5% Yeild Or Higher")
CustomBlock(leading: "App Help", trailing: "More Apps")
}
}
}
}
struct CustomBlock: View {
var leading: String
var trailing: String
var body: some View {
VStack {
HStack {
Button(action: {}){
Text(leading)
.bold()
.font(Font.custom("Helvetica Neue", size: 21.0))
.padding(18)
.foregroundColor(Color.white)
.background(Color.green)
.cornerRadius(12)
}
Button(action: {}){
Text(trailing)
.bold()
.font(Font.custom("Helvetica Neue", size: 21.0))
.padding(18)
.foregroundColor(Color.white)
.background(Color.green)
.cornerRadius(12)
}
}
}.padding()
}
}

Resources