Two buttons in swift UI on watchOS - swiftui

I have a view which I would like to use as a List item on WatchOS.
The view has a ZStack which has one VStacks in it and HStack on top of it. The top HStack has two buttons in it, each taking half of the list item. The idea is to have two transparent buttons on top of the text in the list item with two different actions based on which one is tapped.
When I tap one of the buttons in the list. Both of the action callbacks get triggered. I found this post where the Button style had to be changed to BorderlessButtonStyle to have the callback work but this is not available on watchOS.
Another solution was to use .onTapGesture {} on the button which works for me when the button has some color(check image below for reference).
But when I set the color of the buttons to clear and it looks as I want it, the .onTapGesture {} is not called anymore and only the action callback fires.
Is it even possible to make this work with SwiftUI and using List?
Here is my view code.
var body: some View {
ZStack(content: {
VStack(alignment: .leading, spacing: nil, content: {
Text(goal.title).foregroundColor(.red).padding(.leading, 8)
.padding(.trailing, 8)
.padding(.top, 4)
HStack(alignment: .center,content: {
Text("0").padding(.top,8).padding(.trailing, 16).padding(.bottom, 4).font(.title2)
}).frame(minWidth: 0, maxWidth: .infinity, minHeight: 0,maxHeight: 35,alignment: .trailing)
}).listRowPlatterColor(Color.blue).frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, alignment: .leading).background(Color(UIColor.goalBackroundColor)).cornerRadius(10)
HStack(alignment: .center, spacing: nil, content: {
Button(action: {
//print("\(goal.id) -1")
print("huehue")
}){
Text("").frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .center)
}.background(Color.clear).onTapGesture {
print("\(goal.id) -1")
}
Button(action: {print("hue")}){
Text("").frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .center)
}.background(Color.clear).onTapGesture {
print("\(goal.id) +1")
}
}).frame(minWidth: 0, maxWidth: .infinity, minHeight: 0,maxHeight: .infinity,alignment: .center)
})
}

Related

Button has two different colors, even if only specified one

I want to fit a button to the whole screen of my apple watch. But there is a lighter red and a dark red like in this picture.
This is my code: I want the whole screen to have only one color if this is possible. Thanks
Button(action: {
Task {
if (!muteWatch) {
WKInterfaceDevice.current().play(.failure)
}
Task {
await isWorkingSince()
}
}
}, label: {
Text("Stop ".localized() + stopTimeString).padding().frame(
minWidth: 0,
maxWidth: .infinity,
minHeight: 0,
maxHeight: .infinity,
alignment: .topLeading
).background(Color.red)
}).padding().frame(
minWidth: 0,
maxWidth: .infinity,
minHeight: 0,
maxHeight: .infinity,
alignment: .topLeading
).background(Color.red)
You need plain button style, like
Button(action: {}) {
Text("Hello, World!")
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(.red)
}
.buttonStyle(.plain) // << here !!
Tested with Xcode 13.4 / watchOS 8.5
Button(action: {self. buttonTapped()}) {
Text("Button")
padding(. all, 12)
foregroundColor(. white)
background(Color. red)
}

SwiftUI: Resize spacers in VStack on smaller devices in a certain way

I am trying to optimize a UI with VStacks and spacers in between. The UI is ideally designed for the iPhone 13 Pro screen size (left). For smaller devices, the intention is that the spacers will shrink in a certain way that the UI still looks appealing.
I tried to achieve this by using frames for the Spacers with minHeight, idealHeight and maxHeight. The intended layout appears on the iPhone 13 Pro, but on a smaller device like the iPhone SE the spacers don't scale down to the minWidth. How can I fix that?
import SwiftUI
struct ContentView: View {
var body: some View {
VStack(spacing: 0) {
// Top Space
Spacer()
.frame(minHeight: 16, idealHeight: 32, maxHeight: .infinity)
.fixedSize()
// VStack 1
VStack(spacing: 0) {
// Image placeholder
Rectangle()
.fill(Color.red)
.frame(height: 175)
Spacer()
.frame(minHeight: 15, idealHeight: 62, maxHeight: .infinity)
.fixedSize()
Text("Abc")
.frame(height: 100)
}
.background(Color.gray)
// Middle Space
Spacer()
.frame(minHeight: 22, idealHeight: 100, maxHeight: .infinity)
.fixedSize()
// VStack 2
VStack(spacing: 0) {
// Image placeholder
Rectangle()
.fill(Color.red)
.frame(height: 100)
Spacer()
.frame(minHeight: 15, idealHeight: 35, maxHeight: .infinity)
.fixedSize()
// Image placeholder
Rectangle()
.fill(Color.red)
.frame(height: 195)
}
.background(Color.gray)
// Bottom Space
Spacer()
.frame(minHeight: 16, idealHeight: 45, maxHeight: .infinity)
.fixedSize()
}
.edgesIgnoringSafeArea(.all)
}
}
I'd suggest to wrap everything in another VStack and use it's spacing.
You can read out the UIScreen bounds in the init of the view and compare it to the bounds of all devices.
struct SpacerTest: View {
var spacing: CGFloat
init() {
let screenHeight = UIScreen.main.bounds.size.height
if screenHeight < 500 {
self.spacing = 20
} else if screenHeight < 600 {
self.spacing = 50
} else {
self.spacing = 100
}
}
var body: some View {
VStack(spacing: spacing) {
Text("Spacing Test")
Text("Spacing Test")
}
}
}

pin view to bottom of screen and ignore bottom safe area

Layout question - how do I setup for a view block to expand over the bottom safe area? I've looked through various sources for ignoresSafeAreas() but can't achieve quite the result I'm looking for.
I want, later, to be able to expand this view upwards but start it short. If that makes sense.
var body: some View {
VStack{
Spacer()
HStack(alignment: .center) {
Text ("Expand to fill bottom safe area ...?")
.foregroundColor(.white)
}
.frame(minWidth: 100, maxWidth: .infinity, minHeight: 50, maxHeight: 100)
.background(Color.red)
.ignoresSafeArea()
}
}
Option 1
Put ignoresSafeArea inside background. This will let the red color extend over to the device edges, but the HStack's position will stay the same.
struct ContentView: View {
var body: some View {
VStack{
Spacer()
HStack(alignment: .center) {
Text ("Expand to fill bottom safe area ...?")
.foregroundColor(.white)
}
.frame(minWidth: 100, maxWidth: .infinity, minHeight: 50, maxHeight: 100)
.background(Color.red.ignoresSafeArea()) /// inside `background`
}
}
}
Option 2
Put ignoresSafeArea on the VStack, and everything will ignore the safe area.
struct ContentView: View {
var body: some View {
VStack{
Spacer()
HStack(alignment: .center) {
Text ("Expand to fill bottom safe area ...?")
.foregroundColor(.white)
}
.frame(minWidth: 100, maxWidth: .infinity, minHeight: 50, maxHeight: 100)
.background(Color.red)
}
.ignoresSafeArea() /// attached to `VStack`
}
}
Result:
Option 1
Option 2

How to create Grids View in SwiftUI inside a ScrollView?

I need to create a simple Grid View inside a ScrollView in my SwiftUI project.
Basically, I need to place a few Buttons side by side (2 in a row) in the scrollview.
But when I compile my app, the buttons are underneath each other!
I need to create something like this:
This is my code:
ScrollView(.vertical){
Spacer(minLength: 10)
Button(action: { }) {
Image("mountain")
//declare your image as resizable, otherwise it will keep its original size
.resizable()
//declare the frame of the image (i.e. the size you want it to resize to)
.frame(width: 160, height: 200)
// place the red rectangle with text in an overlay
// as opposed to HStack where elements are side by side
// here the image will be placed under the rest
.overlay(
//This HStack places the text in the middle with some padding
VStack(spacing: 0) {
Text("Learning to")
.font(Font.custom("Nagietha-Regular", size: 40))
.foregroundColor(.white)
// .padding(.vertical)
Text("Meditate")
.font(.title)
.foregroundColor(.white)
.fontWeight(.heavy)
//.padding(.vertical)
}
//set the background of the text to be semitransparent red
.background(Color.clear.opacity(0.6)),
//this defines the alignment of the overlay
alignment: .center)
//clip everything to get rounded corners
.clipShape(RoundedRectangle(cornerRadius: 10))
}
Button(action: { }) {
Image("mountain")
//declare your image as resizable, otherwise it will keep its original size
.resizable()
//declare the frame of the image (i.e. the size you want it to resize to)
.frame(width: 160, height: 200)
// place the red rectangle with text in an overlay
// as opposed to HStack where elements are side by side
// here the image will be placed under the rest
.overlay(
//This HStack places the text in the middle with some padding
VStack(spacing: 0) {
Text("Learning to")
.font(Font.custom("Nagietha-Regular", size: 40))
.foregroundColor(.white)
// .padding(.vertical)
Text("Meditate")
.font(.title)
.foregroundColor(.white)
.fontWeight(.heavy)
//.padding(.vertical)
}
//set the background of the text to be semitransparent red
.background(Color.clear.opacity(0.6)),
//this defines the alignment of the overlay
alignment: .center)
//clip everything to get rounded corners
.clipShape(RoundedRectangle(cornerRadius: 10))
}
}
Could someone please advice on this?
struct SimpleGridView: View {
#State var yourArray: [Int] = [1,2,3,4,5,6,7,8,9]
var columns: [GridItem] =
Array(repeating: .init(.flexible()), count: 2)
#State var selection: Int = 1
var body: some View {
VStack{
Picker(selection: $selection, label: Text("GridStyles"), content: {
Text("Easily grows").tag(1)
Text("manual").tag(2)
}).pickerStyle(SegmentedPickerStyle())
switch selection {
case 1:
ScrollView{
LazyVGrid(columns: columns){
//This doesnt have to be a loop but it would make it scalable
ForEach(yourArray, id: \.self){ item in
Button(action: {
print(item.description)
}, label: {
Rectangle().overlay(Text(item.description).foregroundColor(.white)).foregroundColor(.blue).frame(minWidth: /*#START_MENU_TOKEN#*/0/*#END_MENU_TOKEN#*/, idealWidth: /*#START_MENU_TOKEN#*/100/*#END_MENU_TOKEN#*/, maxWidth: /*#START_MENU_TOKEN#*/.infinity/*#END_MENU_TOKEN#*/, minHeight: /*#START_MENU_TOKEN#*/0/*#END_MENU_TOKEN#*/, idealHeight: /*#START_MENU_TOKEN#*/100/*#END_MENU_TOKEN#*/, maxHeight: /*#START_MENU_TOKEN#*/.infinity/*#END_MENU_TOKEN#*/, alignment: /*#START_MENU_TOKEN#*/.center/*#END_MENU_TOKEN#*/)
})
}
}
}
case 2:
VStack{
HStack{
Button(action: {
print(yourArray[0].description)
}, label: {
Rectangle().overlay(Text(yourArray[0].description).foregroundColor(.white)).foregroundColor(.blue).frame(minWidth: /*#START_MENU_TOKEN#*/0/*#END_MENU_TOKEN#*/, idealWidth: /*#START_MENU_TOKEN#*/100/*#END_MENU_TOKEN#*/, maxWidth: /*#START_MENU_TOKEN#*/.infinity/*#END_MENU_TOKEN#*/, minHeight: /*#START_MENU_TOKEN#*/0/*#END_MENU_TOKEN#*/, idealHeight: /*#START_MENU_TOKEN#*/100/*#END_MENU_TOKEN#*/, maxHeight: /*#START_MENU_TOKEN#*/.infinity/*#END_MENU_TOKEN#*/, alignment: /*#START_MENU_TOKEN#*/.center/*#END_MENU_TOKEN#*/)
})
Button(action: {
print(yourArray[1].description)
}, label: {
Rectangle().overlay(Text(yourArray[1].description).foregroundColor(.white)).foregroundColor(.blue).frame(minWidth: /*#START_MENU_TOKEN#*/0/*#END_MENU_TOKEN#*/, idealWidth: /*#START_MENU_TOKEN#*/100/*#END_MENU_TOKEN#*/, maxWidth: /*#START_MENU_TOKEN#*/.infinity/*#END_MENU_TOKEN#*/, minHeight: /*#START_MENU_TOKEN#*/0/*#END_MENU_TOKEN#*/, idealHeight: /*#START_MENU_TOKEN#*/100/*#END_MENU_TOKEN#*/, maxHeight: /*#START_MENU_TOKEN#*/.infinity/*#END_MENU_TOKEN#*/, alignment: /*#START_MENU_TOKEN#*/.center/*#END_MENU_TOKEN#*/)
})
}
HStack{
Button(action: {
print(yourArray[2].description)
}, label: {
Rectangle().overlay(Text(yourArray[2].description).foregroundColor(.white)).foregroundColor(.blue).frame(minWidth: /*#START_MENU_TOKEN#*/0/*#END_MENU_TOKEN#*/, idealWidth: /*#START_MENU_TOKEN#*/100/*#END_MENU_TOKEN#*/, maxWidth: /*#START_MENU_TOKEN#*/.infinity/*#END_MENU_TOKEN#*/, minHeight: /*#START_MENU_TOKEN#*/0/*#END_MENU_TOKEN#*/, idealHeight: /*#START_MENU_TOKEN#*/100/*#END_MENU_TOKEN#*/, maxHeight: /*#START_MENU_TOKEN#*/.infinity/*#END_MENU_TOKEN#*/, alignment: /*#START_MENU_TOKEN#*/.center/*#END_MENU_TOKEN#*/)
})
Button(action: {
print(yourArray[3].description)
}, label: {
Rectangle().overlay(Text(yourArray[3].description).foregroundColor(.white)).foregroundColor(.blue).frame(minWidth: /*#START_MENU_TOKEN#*/0/*#END_MENU_TOKEN#*/, idealWidth: /*#START_MENU_TOKEN#*/100/*#END_MENU_TOKEN#*/, maxWidth: /*#START_MENU_TOKEN#*/.infinity/*#END_MENU_TOKEN#*/, minHeight: /*#START_MENU_TOKEN#*/0/*#END_MENU_TOKEN#*/, idealHeight: /*#START_MENU_TOKEN#*/100/*#END_MENU_TOKEN#*/, maxHeight: /*#START_MENU_TOKEN#*/.infinity/*#END_MENU_TOKEN#*/, alignment: /*#START_MENU_TOKEN#*/.center/*#END_MENU_TOKEN#*/)
})
}
HStack{
Button(action: {
print(yourArray[4].description)
}, label: {
Rectangle().overlay(Text(yourArray[4].description).foregroundColor(.white)).foregroundColor(.blue).frame(minWidth: /*#START_MENU_TOKEN#*/0/*#END_MENU_TOKEN#*/, idealWidth: /*#START_MENU_TOKEN#*/100/*#END_MENU_TOKEN#*/, maxWidth: /*#START_MENU_TOKEN#*/.infinity/*#END_MENU_TOKEN#*/, minHeight: /*#START_MENU_TOKEN#*/0/*#END_MENU_TOKEN#*/, idealHeight: /*#START_MENU_TOKEN#*/100/*#END_MENU_TOKEN#*/, maxHeight: /*#START_MENU_TOKEN#*/.infinity/*#END_MENU_TOKEN#*/, alignment: /*#START_MENU_TOKEN#*/.center/*#END_MENU_TOKEN#*/)
})
Button(action: {
print(yourArray[5].description)
}, label: {
Rectangle().overlay(Text(yourArray[5].description).foregroundColor(.white)).foregroundColor(.blue).frame(minWidth: /*#START_MENU_TOKEN#*/0/*#END_MENU_TOKEN#*/, idealWidth: /*#START_MENU_TOKEN#*/100/*#END_MENU_TOKEN#*/, maxWidth: /*#START_MENU_TOKEN#*/.infinity/*#END_MENU_TOKEN#*/, minHeight: /*#START_MENU_TOKEN#*/0/*#END_MENU_TOKEN#*/, idealHeight: /*#START_MENU_TOKEN#*/100/*#END_MENU_TOKEN#*/, maxHeight: /*#START_MENU_TOKEN#*/.infinity/*#END_MENU_TOKEN#*/, alignment: /*#START_MENU_TOKEN#*/.center/*#END_MENU_TOKEN#*/)
})
}
}
default:
Text("unknown selection")
}
}
}
}

Vertically centering text in Swift UI

I want the "$0.00" to be in the middle of the screen but I can't figure out how to do it.
This is my code:
var body: some View {
NavigationView {
ScrollView {
VStack(alignment: .center) {
Text(String(format: "$%.2f", (dolaresVM.dolares.last?.v)!))
.font(.largeTitle)
}.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
.edgesIgnoringSafeArea(.all)
}.navigationBarTitle("Test")
.onAppear(perform: self.dolaresVM.fetchDolares)
}
}
What am I doing wrong?
ScrollView has infinite inner space for its children. The VStack can't take all of this space. So VStack's height is defined by its content (in our case - Text).
Without ScrollView it will work like you want:
var body: some View {
NavigationView {
VStack(alignment: .center) {
Text(String(format: "$%.2f", 0)).font(.largeTitle)
}.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
.edgesIgnoringSafeArea(.all)
.navigationBarTitle("Test")
}
}
Providing idealHeight for the VStack can be helpful as well. You can use GeometryReader to get the 'outer' height of the ScrollView:
NavigationView {
GeometryReader { geometry in
ScrollView {
VStack(alignment: .center) {
Text(String(format: "$%.2f", 0)).font(.largeTitle)
}.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, idealHeight: geometry.size.height, maxHeight: .infinity)
.edgesIgnoringSafeArea(.all)
}.navigationBarTitle("Test")
}
}
As we discussed above, you don't need ScrollView so can write .navigationBarTitle("Test") inside NavigationView. So that NavigationBarTitle and Text("$0.00") both will be display on your screen.
Here i put static value of Text, you can replace it with dynamic value which you are setting up from your Model.
struct ContentView: View {
var body: some View {
NavigationView {
VStack(alignment: .center) {
Text("$0.00")
.font(.largeTitle)
}
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
.edgesIgnoringSafeArea(.all)
.navigationBarTitle("Test")
}
}
}