How to properly present PKPaymentAuthorizationViewController in a SwiftUI only project? - swiftui

I have visited this answer already and the present method proposed here doesn't work (anymore?). When I use a UIViewControllerRepresentable and then show it as a sheet it looks pretty awful:
If I use overlay it looks exactly like I want it to looks but overlay cannot be triggered from a Button.
Here is the (condensed) code:
public struct ContentView: View {
#ObservedObject var model: RootViewModel
#State private var tappedDonate = false
public var body: some View {
Button(action: {
tappedDonate = true
}, label: {
Text("Donate")
.frame(width: 300, height: 44, alignment: .center)
})
.frame(width: 300, height: 20, alignment: .center)
.padding()
.background(Color.black)
.foregroundColor(.white)
.cornerRadius(22)
.sheet(isPresented: $tappedDonate) {
ApplePayWrapper(request: model.buildApplePayment())
.background(Color.clear)
}
}
public init(model: RootViewModel) {
self.model = model
}
}

I'm not 100% convinced this is the best way to do it, but I managed to make it work visually like I intended it to work using a ZStack:
ZStack {
Button(action: {
tappedDonate = true
}, label: {
Text("Donate")
.frame(width: 300, height: 44, alignment: .center)
})
.frame(width: 300, height: 20, alignment: .center)
.padding()
.background(Color.black)
.foregroundColor(.white)
.cornerRadius(22)
if tappedDonate {
ApplePayWrapper(request: model.buildApplePayment())
}
}
Note that you still need to hook up the cancel button as well as the regular dismiss by tapping outside through the delegate, otherwise the underlying UI with the button does not respond to touch again.

Related

How to increase the size of a Button in SwiftUI? (Multiplatform project)

I am trying to increase the size oaf a Button in SwiftUI, Xcode 12.5 in a Multiplatform project:
Button("Click me") {
// Perform action here
}
.frame(width: 100, height: 100)
.background(Color.yellow)
.buttonStyle(BorderlessButtonStyle())
After checking the API I found that I can style the button but I can not figure out how to make the click box larger. So that the entire yellow frame receives the click action not just the label.
Edit:
The The tap area problem is explained well here: https://alejandromp.com/blog/playing-with-swiftui-buttons/
But the solution there to change add an element to the button and change the size of that does only work in an iOS project. In a Multiplatform project the frame of the button has the wrong size:
Button(action: {}, label: {
Text("Click me")
.frame(width: 100, height: 100)
.background(Color.yellow)
})
Here is a possible solution
For iOS
Button(action: {
//add actions at here
}) {
VStack {
Text("Name")
}.frame(width: 100, height: 100)
.background(Color.yellow)
.cornerRadius(20)
}
For macOS (macOS version works well for iOS version too)
struct ContentView: View {
var body: some View {
VStack{
Button(action: {
//add actions at here
}) {
VStack {
Text("Button Name")
}.frame(width: 100, height: 100)
.background(Color.yellow)
.cornerRadius(20)
}.buttonStyle(CustomButtonStyle())
}
}
}
struct CustomButtonStyle: ButtonStyle {
func makeBody(configuration: Self.Configuration) -> some View {
configuration.label
.foregroundColor(Color.blue)
.cornerRadius(10.0)
.padding()
}
}
I have found this solution where adding contentShape seems to do the trick:
Button(action: doSomething) {
Text("Click me")
.frame(width: 100, height: 100)
.contentShape(Rectangle())
}
.background(Color.yellow)
.buttonStyle(PlainButtonStyle())
The button cannot recognize the modifiers on that place. The solution would be:
Button(action: {}, label: {
Text("Click me")
.frame(width: 100, height: 100)
.background(Color.yellow)
})

How can I change layout of apple ColorPicker from Circle to Square in SwiftUI?

The default button style of ColorPicker is a circle as bellow.
I want to change the style of the circle button to Rectangle. But seems no API can change it style. So I put a Rectangle over it , and set it allowsHitTesting to false to transport click event to ColorPicker.
struct ColorPickerView: View {
#State private var colorValue = Color.orange
var body: some View {
ZStack() {
ColorPicker("", selection: $colorValue)
.labelsHidden()
Rectangle()
.foregroundColor(.blue)
.frame(width: 40, height: 40, alignment: .center)
.allowsHitTesting(false)
}
}
}
But the ColorPicker did not present after click.
I put a circle bellow the Rectangle to test whether allowsHitTesting is useful. It can work properly responding to tap gesture to print "Circle tapped!".
struct ColorPickerView: View {
#State private var colorValue = Color.orange
var body: some View {
ZStack() {
ColorPicker("", selection: $colorValue)
.labelsHidden()
Rectangle()
.foregroundColor(.blue)
.frame(width: 40, height: 40, alignment: .center)
.onTapGesture {
print("Circle tapped!")
}
Rectangle()
.foregroundColor(.blue)
.frame(width: 40, height: 40, alignment: .center)
.allowsHitTesting(false)
}
}
}
Why the ColorPicker can not responding to tap gesture? Or Is there a way to customize the ColorPicker button?
Simply use opacity, and send the ColorPicker to overlay, like in the code:
struct SquareColorPickerView: View {
#Binding var colorValue: Color
var body: some View {
colorValue
.frame(width: 40, height: 40, alignment: .center)
.cornerRadius(10.0)
.overlay(RoundedRectangle(cornerRadius: 10.0).stroke(Color.white, style: StrokeStyle(lineWidth: 5)))
.padding(10)
.background(AngularGradient(gradient: Gradient(colors: [.red,.yellow,.green,.blue,.purple,.pink]), center:.center).cornerRadius(20.0))
.overlay(ColorPicker("", selection: $colorValue).labelsHidden().opacity(0.015))
.shadow(radius: 5.0)
}
}
Use case:
struct ContentView: View {
#State private var colorValue = Color.orange
var body: some View {
SquareColorPickerView(colorValue: $colorValue)
}
}

How can I make my TextField starts from Top-leading in SwiftUI?

I have a simple code of TextField, which has a frame and background, the Text starts from Center-leading which I want it start from Top-leading, how can I do this? thanks
import SwiftUI
struct ContentView: View {
#State private var stringOfText: String = "Hello, world!"
var body: some View {
TextField("", text: $stringOfText)
.frame(width: 300, height: 200, alignment: .center)
.background(Color.yellow)
}
}
You can try:
.frame(width: 300, height: 200, alignment: .top)

How I can have transform animation in SwiftUI?

Here I got 2 shapes, one Rectangle and one Circle, which with action of a Button only one of them became visible to user, I tried to use #Namespace for this transform, but did not panned out!
MY Goal: Having a nice and smooth transform animation from one Shape to other.
struct ContentView: View {
#State var action: Bool = false
#Namespace var sameShape
var body: some View {
ZStack
{
Group
{
if action
{
Circle()
.fill(Color.blue).frame(width: 150, height: 150, alignment: .center)
.matchedGeometryEffect(id: "Dakota148Zero", in: sameShape)
}
else
{
Rectangle()
.fill(Color.red).frame(width: 150, height: 150, alignment: .center)
.matchedGeometryEffect(id: "Dakota148Zero", in: sameShape)
}
}
.animation(.easeInOut)
VStack
{
Spacer()
Button("transform") { action.toggle() }.font(Font.largeTitle).padding()
}
}
}
}
Here is way to do it by representing the circle and square as a RoundedRectangle with different cornerRadius values:
struct ContentView: View {
#State var action = false
var body: some View {
ZStack
{
RoundedRectangle(cornerRadius: action ? 0 : 75)
.fill(action ? Color.red : .blue)
.frame(width: 150, height: 150, alignment: .center)
.animation(.easeInOut)
VStack
{
Spacer()
Button("transform") {
action.toggle()
}
.font(Font.largeTitle)
.padding()
}
}
}
}
Group is not real container, so don't store animation. Replace Group with some stack, like
VStack
{
if action
// ... other code no change
With iOS14 out, you can use matchedGeometryEffect(). If you are using iOS14, I would recommend this approach.
https://www.hackingwithswift.com/quick-start/swiftui/how-to-synchronize-animations-from-one-view-to-another-with-matchedgeometryeffect
https://developer.apple.com/documentation/swiftui/view/matchedgeometryeffect(id:in:properties:anchor:issource:)
So in your solution, if you replace action.toggle() with withAnimation{self.action.toggle()} in your button code, it will animate.
Button("transform") {
withAnimation{self.action.toggle()}
}
.font(Font.largeTitle).padding()
This solution works on the simulator for me (Xcode 12.1, iPhone 11 iOS 14.1):
import SwiftUI
struct ContentView: View {
#State var action: Bool = false
#Namespace var transition
var body: some View {
ZStack {
Group {
if action {
Circle()
.fill(Color.blue).frame(width: 150, height: 150, alignment: .center)
.matchedGeometryEffect(id: "shape", in: transition)
} else {
Rectangle()
.fill(Color.red).frame(width: 150, height: 150, alignment: .center)
.matchedGeometryEffect(id: "shape", in: transition)
}
}
.animation(.easeInOut)
VStack {
Spacer()
Button("transform") { withAnimation{self.action.toggle()} }.font(Font.largeTitle).padding()
}
}
}
}
The matchedGeometryEffect() doesn't want to animate different shapes (including cornerRadius) or colors that nicely, not sure if this is a bug that will get fixed in future patches or just a feature that needs to be worked around by regular animations. With me playing around with matchedGeometryEffect(), it seems to do great with sizing things up and down, like shown with this code:
import SwiftUI
struct ContentView: View {
#State private var animate: Bool = false
#Namespace private var transition
var body: some View {
VStack {
if animate {
RoundedRectangle(cornerRadius: 75.0)
.matchedGeometryEffect(id: "shape", in: transition)
.frame(width: 250, height: 250, alignment: .center)
.foregroundColor(Color.blue)
.animation(.easeInOut)
.onTapGesture {
animate.toggle()
}
} else {
// Circle
RoundedRectangle(cornerRadius: 75.0)
.matchedGeometryEffect(id: "shape", in: transition)
.frame(width: 150, height: 150, alignment: .center)
.foregroundColor(Color.red)
.animation(.easeInOut)
.onTapGesture {
animate.toggle()
}
}
}
}
}

SwiftUI, is there a traitCollectionDidChange-like method in a View?

I'm researching SwiftUI to see if it fits our compony's use case. However, I find it rather bare bones and wonder wether I am missing some key insights.
Currently I am researching if V/HStack's can respond dynamically to Accesability font-size changes.
The issue is that I need to give the items a frame, it does not automatically pin the outer Stacks items-view constraints to their inner content. When the user sets the font-size to full blown Bananas-mode the UI just breaks.
struct PatientHistoryView: View {
#State var model : PatientHistoryModel = historyModel;
var body : some View {
GeometryReader { g in
ScrollView(.vertical, showsIndicators: true) {
List
{
ForEach(self.model.data)
{ labResults in
LabDetailView(labeResults: labResults)
}
}.frame(width: 300, height: g.size.height).background(Color.clear)
}
}
}
}
adding these properties do not seem to affect the Font-scaling issues:
struct PatientHistoryView: View {
#State var model : PatientHistoryModel = historyModel;
var body : some View {
GeometryReader { g in
ScrollView(.vertical, showsIndicators: true) {
List
{
ForEach(self.model.data)
{ labResults in
LabDetailView(labeResults: labResults).frame(minWidth: 200, idealWidth: 400, maxWidth: 600, minHeight: 50, idealHeight: 100, maxHeight: 600)
}
}.frame(width: 300, height: g.size.height).background(Color.clear)
}
}
}
}
Here is the labDetailView
struct LabDetailView: View {
var labeResults : LabResults
var color : Color = .red
var spacer : some View {
Spacer().frame(width: 10, height: 0)
}
var body : some View {
GeometryReader { g in
HStack
{
Group
{
VerticalLineView().frame(width: 10, height: g.size.height, alignment: .leading).background(self.color)
self.spacer
self.spacer
LineStack(lineColor: .white, lineHeight: 2).frame(width: 20, height: 20, alignment: .center)
self.spacer
self.spacer
}
VStack(alignment: .leading, spacing: 8)
{
Text(self.labeResults.title).fontWeight(.bold)
Text(self.labeResults.type)
}
Spacer()
VStack(alignment: .center)
{
Text(self.labeResults.max.asString)
Spacer()
Text(self.labeResults.avg.asString).fontWeight(.bold).font(.system(size: 20))
Spacer()
Text(self.labeResults.min.asString)
}.frame(width: 50, height: g.size.height, alignment: .center)
self.spacer
self.spacer
}.frame(width: g.size.width, height: g.size.height, alignment: .leading)
}
}
}
In UIkit these issues can be resolved in the traitCollectionDidChange hook. However, in Swift UI I just cannot find an equivalent.
Doe anybody have any ideas?
Thanks!