Section transition in SwiftUI Form - ios

The Section transition in Form is always left-right, I would like to change it to right-left in some cases, I tried .transition with .move modifier, but it does not have effect.
struct ContentView: View {
#State var visible = true
var body: some View {
Form {
Button("visible") {
self.visible.toggle()
}
visible ? Section {
Text("Section 1")
Text("Section 1")
}.transition(.move(edge: .leading)) : nil
!visible ? Section {
Text("Section 2")
Text("Section 2")
}.transition(.move(edge: .trailing)) : nil
Section {
Text("Section 3")
Text("Section 3")
} // Section 3 should not be animated
}.animation(.linear(duration: 0.5))
}
}

I read through a significant amount of Apple's code documentation for SwiftUI forms and it appears that there are very few options for customization; specifically with the way you have the code currently set up. In that, I would recommend creating the animations and page from scratch. Doing it that way, you would have complete creative freedom of the directions for the transitions. This would take a lot of work to look like a Form, but it's possible. The following answer will give you a right-left transition: https://stackoverflow.com/a/62144939/13296047

Related

How the layoutPriority values work in SwiftUI?

I was experimenting with layoutPriority() modifier. First I set its value as 1. Then I set like below:
layoutPriority(0)
I have not observed any changes. Can someone explain?
You use layoutPriority when there is a race where you need to prefer some ui over another like this one Hackingwithswift
struct ContentView: View {
var body: some View {
HStack {
Text("This line will take more space than the one below.")
Text("small short one.")
}
.font(.largeTitle)
}
}
But after adding
struct ContentView: View {
var body: some View {
HStack {
Text("This line will take more space than the one below.")
Text("small short one.").layoutPriority(1)
}
.font(.largeTitle)
}
}

What is the correct way to pass touches through a view to those below it?

I have a SwiftUI view that is displayed over other views, and have found that using Color.clear like this below seems to allow touch interactions to pass through to anything under it:
var body: some View {
VStack {
Spacer()
HStack {
Spacer()
SomeCustomContent()
Spacer()
}
.overlay(GeometryReader { proxy in
Color.clear.preference(key: MyCustomHeightPreferenceKey.self, value: proxy.size.height)
})
}
}
Is this the correct way to make touches pass through to the views below, or it this just a coincidental quirk/bug in SwiftUI behaviour that Apple might fix/change as swiftui matures?
If not, what is the correct way to pass the touches through?
You can pass through touch events without use a clear color like this:
var body: some View {
Rectangle()
.overlay(
Circle()
.fill(.blue)
.allowsHitTesting(false) // <--- Passes through gestures
)
}
Asperi mentioned this solution in a comment above, and you can also find a good blog about this topic here: https://www.hackingwithswift.com/books/ios-swiftui/disabling-user-interactivity-with-allowshittesting

SwiftUI onDelete cannot detect tap when onTapGesture is added

I am developing an iOS app with list by SwiftUI. I am implementing .onDelete to enable user to delete the rows. However, I have found that when I add a .onTapGesture to the VStack View containing the List, the onDelete function is not called when the user tapped the "Delete" button after slide the row left. However, it stills works when the user slide the row to the left side to delete this. It seems that the .onTapGesture blocks .onDelete to receive user input. How to solve this?
NavigationView {
VStack(alignment: .leading) {
List {
ForEach(things) { thing in
Text(thing)
}
.onDelete(perform: { indexSet in
things.remove(atOffsets: indexSet)
})
}
.listStyle(SidebarListStyle())
}
.onTapGesture {
}
}
Here is some code that can show my problem.
This is probably useless and very hacky but... If you must have an area that is tappable, you could place everything in a ZStack and put a random view over the List and make that tappable. Then you could set a good amount of padding to free the area where the delete button would be tapped, like this:
NavigationView {
ZStack {
List {
ForEach(things, id: \.self) { thing in
Text(thing)
}
.onDelete(perform: { indexSet in
things.remove(atOffsets: indexSet)
})
}
.zIndex(2)
.listStyle(SidebarListStyle())
Rectangle()
.zIndex(3)
.fill(Color.red)
.padding(.trailing, 80)
.allowsHitTesting(true)
.onTapGesture {
print("Blocking you")
}
}
}
I put the color in so you can see exactly where you are working, the code above would give you a look like this:
Tappable Rectangle on List
When you're happy with the area that the rectangle covers, you can just set the Opacity to a very small amount, just by replacing the color with
.opacity(0.000001)
or something. Unfortunately taps don't work with Color.clear or hidden() modifiers.

Not possible to control animations in a Form?

Is it not meant to be possible to control the animations that take place inside of a Form view? I have here a playground that demonstrates the issue, along with a gif of what happens. As you can see, my transition on the 2nd animated view is completely ignored, and I had to manually slow down the video because the durations are ignored, too.
I don't really want a scaling transition, this was just to demonstrate that no matter what I put in there the animation is the same. Is that expected, or is it a bug? Or am I just doing something totally wrong?
It's also not clear to me why the animation of the VStack is handled so differently than the simple Text field, which slides down nicely while the VStack seems to be getting some combination of .move and .opacity.
import SwiftUI
import PlaygroundSupport
struct ContentView: View {
#State var showGoodAnimation = false
#State var showBadAnimation = false
var body: some View {
Form {
Toggle(isOn: self.$showGoodAnimation.animation(.easeInOut(duration: 1))) {Text("Yay!")}
if self.showGoodAnimation {
Text("I animate beautifully.")
}
Toggle(isOn: self.$showBadAnimation.animation(.easeInOut(duration: 1))) {Text("Boo!")}
if self.showBadAnimation {
VStack {
Text("Hi.").padding()
Text("I'm a hot mess.").padding()
}
.frame(height: 250)
.transition(.scale)
}
Text("I'm just always here.")
}
}
}
PlaygroundPage.current.setLiveView(ContentView())
At a guess, probably worked around this question some time ago, but for the benefit of those beating their head's against SwiftUI Form and the like now (as I was :-) )
It turns out that Forms, Lists and (no doubt) other components purposely ignore animation customisation because they are "higher-level" SwiftUI View components (unlike V and HStack's).
They do this because SwiftUI's higher-level components are intended to convey semantic information and (more practically) to work well across all platforms. To achieve this, Apple's engineering decision has been to make animation "easy", but (as observed) only to the extent of essentially turning it "on" or "off".
It's likely engineered like this because if a developer wants more control. Then Apple believes encouraging them to use the lower-level components will be less painful than trying to work around the optimisations they have applied to their higher-level view components.
Anyway, for the determined, there is at least one escape hatch via wrapping the View in a container and specifying the .animation(nil) modifier (as mentioned in Asperi's SO answer here.
An example of this is shown below for completeness; personally, I'm avoiding this pattern as I suspect it's a bit of a footgun.
import PlaygroundSupport
import SwiftUI
struct ContentView: View {
#State var showGoodAnimation = false
#State var showBadAnimation = false
var body: some View {
Form {
Toggle(isOn: self.$showGoodAnimation.animation(.easeInOut(duration: 1))) { Text("Yay!") }
if self.showGoodAnimation {
Text("I animate beautifully.")
}
Toggle(isOn: self.$showBadAnimation.animation()) { Text("Boo!") }
VStack {
if self.showBadAnimation {
List {
Text("I animated differently").padding()
Text("But am I a footgun?").padding()
}
.transition(.asymmetric(insertion: .slide, removal: .opacity))
.animation(.easeOut(duration: 5))
}
}
.animation(nil)
.transition(.slide)
Text("I'm just always here.")
}
}
}
PlaygroundPage.current.setLiveView(ContentView())

Button within stack within form is active outside of bounds

I had some weird behavior in SwiftUI that I did not understand, and I simplified it to this:
struct ContentView: View {
#State var name: String = ""
var body: some View {
Form {
VStack {
Text("Line 1")
Text("Line 2")
Text("Line 3")
Text("Line 4")
Button(action: {
print("hello world")
}) { Text("Print hello world")}
}
}
}
}
This makes the "Line 1" ... "Line 4" texts be part of the "Print hello world" button.
If I remove the VStack in the Form they are not.
I am not sure if this is a bug in SwiftUI or if I am not understanding something that maybe I should try to understand, so: does anybody understand why Line 1 ... Line 4 would be part of the button here?
This is not a bug, Form is a List under the hood, so when you create a VStack in it you create a row in the list, but if you remove the VStack there are 5 rows (4 Texts and one Button)

Resources