I'm trying to make a view in SwiftUI as shown below using SwiftUI.
Below is the code I have written, but I'm not sure what will the best way to code this is. My code looks like below with hard-coded values.
var body: some View {
TestView {
VStack {
HStack {
Image("profile")
.padding(.top, 30)
.padding(.leading, 30)
Spacer()
}
HStack {
Text("UserName")
.font(.system(size: 20, weight: .bold, design: .default))
.foregroundColor(.gray)
.padding(.leading, 30)
Spacer()
}
.padding(.bottom, 1)
HStack(spacing: 0) {
Text("4")
.font(.system(size: 42, weight: .bold, design: .default))
.foregroundColor(.pink)
VStack {
Text("/10")
.font(.system(size: 25, weight: .bold, design: .default))
.foregroundColor(.gray)
Spacer()
Spacer()
Spacer()
Spacer()
}
}
Image("stars")
.resizable()
.padding(.leading, 20)
.padding(.trailing, 20)
.padding(.bottom, 5)
}
}
.frame(width: 200, height: 250, alignment: .center)
}
the output looks like this
I believe this should not be the correct way to do this, with multiple spacers, too many adjustments.
I'm new to SwiftUI, though the same UI in xib/storyboard won't take me more than 10-15mins with constraints.
Can anyone suggest a better way?
P.S. please ignore color and font size
-----------------------------Updated code----------------------------
var body: some View {
TestView {
VStack(alignment: .leading) {
Image("profile") //Async-image
.offset(x: 30)
VStack(alignment: .center) {
Text("UserName")
.font(Font(nameFont))
ZStack(alignment: Alignment(horizontal: .leading, vertical: .center)) {
HStack(alignment: .firstTextBaseline) {
Text("4")
.font(Font(bigFont))
.foregroundColor(.pink)
}
HStack(alignment: .firstTextBaseline, spacing: 0) {
Text("4")
.font(Font(bigFont))
.opacity(0)
Text("/5")
.font(Font(smallFont))
.baselineOffset((bigFont.capHeight))
.foregroundColor(.gray)
}
}
Image("stars") //view with rating logic
.resizable()
.frame(height: 30)
.padding(.leading, 20)
.padding(.trailing, 20)
.padding(.bottom, 5)
}
}
}
.frame(width: 200, height: 250, alignment: .center)
}
Could avoid Spacers and VStack
HStack(spacing: 0) {
Text("4")
.font(.system(size: 42, weight: .bold, design: .default))
.foregroundColor(.pink)
Text("/10")
.font(.system(size: 25, weight: .bold, design: .default))
.foregroundColor(.gray)
.offset(y: -20)
}
Related
I have list of (several) products in cart. Each product has it's corresponding view created from generic view by passing product's object. View of single product in cart has two button (+ and -) to control the amount of the product in cart. Unfortunately, when products views are embedded in List (and then ForEach) these two buttons do not work because clicking on the whole product view leads to another detailed view (which I want to leave as it is). I need ForEach to be embedded in List because I want to use .onDelete modifier to easily delete the products from list.
What I want is those two buttons (+ and -) work.
List View:
List {
ForEach(Array(cartViewModel.cart.products.keys).sorted { $0.id > $1.id}, id: \.self) { product in
ProductTileForCartView(product: product)
.environmentObject(cartViewModel)
.onTapGesture {
withAnimation(.interactiveSpring(response: 0.6, dampingFraction: 0.7, blendDuration: 0.7)) {
cartViewModel.changeFocusedProductFor(product: product)
}
}
}
.onDelete(perform: cartViewModel.removeProducts)
}
SingleProductView:
HStack(alignment: .center) {
KFImage(URL(string: product.imagesURLs.first!)!)
.placeholder {
Image("product_placeholder_image")
.resizable()
.aspectRatio(contentMode: .fit)
.clipShape(RoundedRectangle(cornerRadius: 15))
}
.retry(maxCount: 3, interval: .seconds(3))
.cancelOnDisappear(true)
.resizable()
.aspectRatio(contentMode: .fit)
.clipShape(RoundedRectangle(cornerRadius: 15))
.padding([.vertical, .trailing])
VStack(alignment: .leading) {
VStack(alignment: .leading, spacing: 10) {
Text(product.company)
.font(.system(size: 14, weight: .regular, design: .rounded))
.foregroundColor(.gray)
.fixedSize(horizontal: false, vertical: true)
Text(product.name)
.font(.system(size: 22, weight: .heavy, design: .rounded))
.fixedSize(horizontal: false, vertical: true)
.foregroundColor(colorScheme == .light ? .black : .white)
Text("$\(product.price, specifier: "%.2f")")
.font(.system(size: 20, weight: .bold, design: .rounded))
.foregroundColor(.accentColor)
}
Spacer()
if includeButtonsForAmountChange {
HStack(spacing: 20) {
Button {
withAnimation {
cartViewModel.removeProductFromCart(product: product, quantity: 1)
}
} label: {
Image(systemName: "minus.square.fill")
.resizable()
.frame(width: 30, height: 30)
}
Text("\(cartViewModel.getCartProductCount(product: product))")
.font(.system(size: 22, weight: .heavy, design: .rounded))
Button {
withAnimation {
cartViewModel.addProductToCart(product: product, quantity: 1)
}
} label: {
Image(systemName: "plus.square.fill")
.resizable()
.frame(width: 30, height: 30)
}
}
}
}
.padding()
}
How the view look:
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)
Disclaimer: New to SwiftUI
I know a ZStack() view allows me to overlap views, which is what I want for the heading section of the app.
I set the ZStack() to not take up the entire height and had expected an HStack(), placed after the ZStack() to do just that, appear after. Instead it also overlaps with the ZStack.
I'm sure it's a simple solution to co-exist. Image and code below.
var body: some View {
GeometryReader { geometry in
ZStack(alignment: .topLeading) {
Ellipse()
.fill(self.bgColor)
.frame(width: geometry.size.width * 1.4, height: geometry.size.height * 0.28)
.position(x: geometry.size.width / 2.35, y: geometry.size.height * 0.1)
.shadow(radius: 3)
.edgesIgnoringSafeArea(.all)
HStack(alignment: .top) {
VStack(alignment: .leading) {
Text(self.title)
.font(.title)
.fontWeight(.bold)
.foregroundColor(Color.white)
Text(self.subtitle)
.font(.subheadline)
.fontWeight(.regular)
.foregroundColor(Color.white)
}
.padding(.leading, 25)
.padding(.top, 20)
Spacer()
VStack(alignment: .trailing) {
Image("SettingsIcon")
.resizable()
.scaledToFit()
.frame(width: 30, height: 30)
}
.padding(.top, 20)
.padding(.trailing, 25)
}
}
.fixedSize(horizontal: false, vertical: true)
HStack(alignment: .top) {
Text(self.title)
.font(.title)
.fontWeight(.bold)
.foregroundColor(Color.white)
}
}
}
Try the following code using a top VStack and closing the GeometryReader before the last HStack:
var body: some View {
VStack { // <-- here
GeometryReader { geometry in
ZStack(alignment: .topLeading) {
Ellipse()
.fill(self.bgColor)
.frame(width: geometry.size.width * 1.4, height: geometry.size.height * 0.28)
.position(x: geometry.size.width / 2.35, y: geometry.size.height * 0.1)
.shadow(radius: 3)
.edgesIgnoringSafeArea(.all)
HStack(alignment: .top) {
VStack(alignment: .leading) {
Text(self.title)
.font(.title)
.fontWeight(.bold)
.foregroundColor(Color.white)
Text(self.subtitle)
.font(.subheadline)
.fontWeight(.regular)
.foregroundColor(Color.white)
}
.padding(.leading, 25)
.padding(.top, 20)
Spacer()
VStack(alignment: .trailing) {
Image("SettingsIcon")
.resizable()
.scaledToFit()
.frame(width: 30, height: 30)
}
.padding(.top, 20)
.padding(.trailing, 25)
}
}.fixedSize(horizontal: false, vertical: true)
} // <-- here end GeometryReader
HStack(alignment: .top) {
Text(self.title)
.font(.title)
.fontWeight(.bold)
.foregroundColor(Color.white)
}
} // <-- here end VStack
}
Im trying to push one GeometryReader as a button to the bottom of the screen but Spacer doesn't work here...
The idea is to make the app responsive to all screen sizes.
VStack {
GeometryReader { geometry in
Text("VeganFood")
.font(.system(size: geometry.size.width/12, weight: .bold))
.foregroundColor(.white)
.frame(maxWidth: .infinity)
.frame(maxHeight: 200)
}
/// Spacer doesn't work here
GeometryReader { geometry in
Button(action: { }) {
Text("Let's get started!")
.font(.system(size: geometry.size.width/30, weight: .medium, design: .rounded))
.frame(width: geometry.size.width/3, height: geometry.size.height/7)
.foregroundColor(.white)
.background(Color.black)
.cornerRadius(20.0)
}
.frame(maxWidth: .infinity)
.frame(maxHeight: 200)
}
}
GeometryReader takes space as much as it can.
struct ContentView: View {
var body: some View {
VStack {
GeometryReader { geometry in
Text("VeganFood")
.font(.system(size: geometry.size.width/12, weight: .bold))
.foregroundColor(.red)
.frame(maxWidth: .infinity)
.frame(maxHeight: 200)
}
GeometryReader { geometry in
VStack { //<= here
Spacer() //<=here
Button(action: { }) {
Text("Let's get started!")
.font(.system(size: geometry.size.width/30, weight: .medium, design: .rounded))
}
.frame(width: geometry.size.width/3, height: geometry.size.height/7)
.foregroundColor(.white)
.background(Color.black)
.cornerRadius(20.0)
.frame(maxWidth: .infinity)
}
}
}
}
}
There might be some different approaches
I have a list of buttons that display perfectly on iOS 13 using SwiftUI, but on iOS 14 it cuts the content off where the screen ends.
Has anything changed with regards to how HStacks renders what isn't on the screen? I used to scroll and be able to see all the buttons.
I will attach some screenshots and the code.
var body: some View {
VStack(alignment: .leading, spacing: 0){
Text("Select a venue type")
.font(.custom("MavenProBold", size: 16))
.padding(.leading, 16)
.padding(.top, 18)
.foregroundColor(Color.black)
ScrollView(.horizontal, showsIndicators: false) {
HStack(alignment: .center, spacing: 4, content: {
Button(action: {
self.selectedButtonIndex = 0
})
{
VStack(alignment: .center, spacing: 0, content: {
ZStack(alignment: .bottomTrailing){
Image(systemName: "star.fill")
.frame(width: circleFrameSize, height: circleFrameSize, alignment: .center)
.font(.title)
.background(Color(hexString: "#1A88FF"))
.foregroundColor(Color.white)
.clipShape(Circle())
}
Text("Things to do")
.padding(.top, 8)
.font(.custom("MavenProBold", size: 12))
.multilineTextAlignment(.center)
.lineLimit(50)
})
.frame(width: 80, height: 80, alignment: .center)
.padding(.all, 10)
.foregroundColor(Color.black)
}
Button(action: {
self.selectedButtonIndex = 0
})
{
VStack(alignment: .center, spacing: 0, content: {
ZStack(alignment: .bottomTrailing){
Image(systemName: "star.fill")
.frame(width: circleFrameSize, height: circleFrameSize, alignment: .center)
.font(.title)
.background(Color(hexString: "#1A88FF"))
.foregroundColor(Color.white)
.clipShape(Circle())
}
Text("Things to do")
.padding(.top, 8)
.font(.custom("MavenProBold", size: 12))
.multilineTextAlignment(.center)
.lineLimit(50)
})
.frame(width: 80, height: 80, alignment: .center)
.padding(.all, 10)
.foregroundColor(Color.black)
}
Button(action: {
self.selectedButtonIndex = 0
})
{
VStack(alignment: .center, spacing: 0, content: {
ZStack(alignment: .bottomTrailing){
Image(systemName: "star.fill")
.frame(width: circleFrameSize, height: circleFrameSize, alignment: .center)
.font(.title)
.background(Color(hexString: "#1A88FF"))
.foregroundColor(Color.white)
.clipShape(Circle())
}
Text("Things to do")
.padding(.top, 8)
.font(.custom("MavenProBold", size: 12))
.multilineTextAlignment(.center)
.lineLimit(50)
})
.frame(width: 80, height: 80, alignment: .center)
.padding(.all, 10)
.foregroundColor(Color.black)
}
Button(action: {
self.selectedButtonIndex = 0
})
{
VStack(alignment: .center, spacing: 0, content: {
ZStack(alignment: .bottomTrailing){
Image(systemName: "star.fill")
.frame(width: circleFrameSize, height: circleFrameSize, alignment: .center)
.font(.title)
.background(Color(hexString: "#1A88FF"))
.foregroundColor(Color.white)
.clipShape(Circle())
}
Text("Things to do")
.padding(.top, 8)
.font(.custom("MavenProBold", size: 12))
.multilineTextAlignment(.center)
.lineLimit(50)
})
.frame(width: 80, height: 80, alignment: .center)
.padding(.all, 10)
.foregroundColor(Color.black)
}
Button(action: {
self.selectedButtonIndex = 0
})
{
VStack(alignment: .center, spacing: 0, content: {
ZStack(alignment: .bottomTrailing){
Image(systemName: "star.fill")
.frame(width: circleFrameSize, height: circleFrameSize, alignment: .center)
.font(.title)
.background(Color(hexString: "#1A88FF"))
.foregroundColor(Color.white)
.clipShape(Circle())
}
Text("Things to do")
.padding(.top, 8)
.font(.custom("MavenProBold", size: 12))
.multilineTextAlignment(.center)
.lineLimit(50)
})
.frame(width: 80, height: 80, alignment: .center)
.padding(.all, 10)
.foregroundColor(Color.black)
}
})
.padding(.leading, 8)
.padding(.trailing, 8)
.padding(.bottom, 8)
}
}
}
I also met this problem. It happens when you have a customized clipShape outside of ScrollView. By customized I mean customized Path of the shape.
From my test, when you use builtin shapes, it works fine, for example:
view.clipShape(Circle())
When you use customized shape but with builtin Path, it also works fine, for example:
view.clipShape(CustomShape())
// which CustomShape is something like:
struct CustomShape: Shape {
func path(in rect: CGRect) -> Path {
return Path(roundedRect: rect, cornerSize: CGSize(width: 10, height: 10)
}
}
But when you use a customized Path in your CustomShape, this issue happens:
view.clipShape(CustomShape())
// which CustomShape is something like:
struct CustomShape: Shape {
func path(in rect: CGRect) -> Path {
let path = UIBezierPath(roundedRect: rect,
byRoundingCorners: corners,
cornerRadii: CGSize(width: radius, height: radius))
return Path(path.cgPath)
}
}
I also tried to draw the path manually(use move, addLine, addArc), it doesn't work.
So the workaround is to use builtin Path in your customized Shape. I guess this is a bug of iOS 14, because it works fine on iOS 13.