shaps80 / SwiftUIBackports

A collection of SwiftUI backports for iOS, macOS, tvOS and watchOS
MIT License
931 stars 59 forks source link

Crash FocusState with two TextFields #61

Closed fpcornelis closed 9 months ago

fpcornelis commented 10 months ago

If you use focus state with two Textfields, app crashes.

If you replace VStack with Form/List it works, but I dont want use a Form.

import SwiftUI
import SwiftUIBackports

struct FocusState: View {
    var body: some View {
        NavigationLink {
            Demo()
                .backport.navigationTitle("Sign In")
        } label: {
            Text("Focus State")
        }
    }
}

private struct Demo: View {
    enum Field: Hashable {
        case username
        case password
    }

    @State private var username = ""
    @State private var password = ""
    @Backport.FocusState private var focusedField: Field?

    var body: some View {
        VStack {
            TextField("Username", text: $username)
                .backport.focused($focusedField, equals: .username)
                .backport.submitLabel(.next)
                .backport.onSubmit {
                    if !username.isEmpty {
                        focusedField = .password
                    }
                }

            TextField("Password", text: $password)
                .backport.focused($focusedField, equals: .password)
                .backport.submitLabel(.done)
                .backport.onSubmit {
                    signIn()
                }

            Button("Sign In") {
                signIn()

            }
        }
        .padding()

        .onAppear {
            focusedField = .username
        }
    }

    private func signIn() {
        if username.isEmpty {
            focusedField = .username
        } else if password.isEmpty {
            focusedField = .password
        } else {
            focusedField = nil
            print(username, password)
        }
    }
}
shaps80 commented 10 months ago

You'll need to provide more details on the crash, stack trace, etc. I have previously used a similar setup without issue.

Ideally a sample project demonstrating the issue, otherwise it could take me a long time to investigate sorry.

elbelga commented 10 months ago

Stacktrace

Thread 0 Crashed:: Dispatch queue: com.apple.main-thread 0 libobjc.A.dylib 0x105b1ee3c bool objc::DenseMapBase<objc::DenseMap<DisguisedPtr, unsigned long, (anonymous namespace)::RefcountMapValuePurgeable, objc::DenseMapInfo<DisguisedPtr>, objc::detail::DenseMapPair<DisguisedPtr, unsigned long>>, DisguisedPtr, unsigned long, (anonymous namespace)::RefcountMapValuePurgeable, objc::DenseMapInfo<DisguisedPtr>, objc::detail::DenseMapPair<DisguisedPtr, unsigned long>>::LookupBucketFor<DisguisedPtr>(DisguisedPtr const&, objc::detail::DenseMapPair<DisguisedPtr, unsigned long>*&) + 4 1 libobjc.A.dylib 0x105b1f9c7 objc::DenseMapBase<objc::DenseMap<DisguisedPtr, unsigned long, (anonymous namespace)::RefcountMapValuePurgeable, objc::DenseMapInfo<DisguisedPtr>, objc::detail::DenseMapPair<DisguisedPtr, unsigned long>>, DisguisedPtr, unsigned long, (anonymous namespace)::RefcountMapValuePurgeable, objc::DenseMapInfo<DisguisedPtr>, objc::detail::DenseMapPair<DisguisedPtr, unsigned long>>::operator[](DisguisedPtr&&) + 35 2 libobjc.A.dylib 0x105b1f97d objc_object::sidetable_retain(bool) + 89 3 BackportsDemo 0x10500b164 Coordinator.responds(to:) + 84 4 BackportsDemo 0x10500b2c7 @objc Coordinator.responds(to:) + 39 5 BackportsDemo 0x10500b221 Coordinator.responds(to:) + 273 6 BackportsDemo 0x10500b2c7 @objc Coordinator.responds(to:) + 39 7 BackportsDemo 0x10500b221 Coordinator.responds(to:) + 273 8 BackportsDemo 0x10500b2c7 @objc Coordinator.responds(to:) + 39 9 BackportsDemo 0x10500b221 Coordinator.responds(to:) + 273 10 BackportsDemo 0x10500b2c7 @objc Coordinator.responds(to:) + 39 11 BackportsDemo 0x10500b221 Coordinator.responds(to:) + 273 12 BackportsDemo 0x10500b2c7 @objc Coordinator.responds(to:) + 39 13 BackportsDemo 0x10500b221 Coordinator.responds(to:) + 273 ....

SwiftUIBackportsDemo-main.zip

shaps80 commented 10 months ago

Interesting, I think this indicates an underlying issue (similar to #62) because they have almost identical configurations for the delegate-dance in order to prevent introducing issues into native SwiftUI code, as well as allow multiple modifiers to co-exist.

@roland-schmitz-ocu worth you taking a look at this as well, I think its related and perhaps even proves my points I made on the other issue.

I'm fairly confident this all worked previously, so I wonder if something was introduced since iOS 16 that just wasn't uncovered until now since a lot of users of this library are here for "backports" therefore not iOS the latest iOS versions.

roland-schmitz-ocu commented 10 months ago

Thanks @shaps80 for the pointer to this issue. I could reproduce it on iOS 15, 16 and 17 and yes you are right, the issue is very similar to #62. Also here the delegate dance happened multiple times which created a cycle in the delegate chain. I could fix it in the same way as in the other two cases by not touching the delegate when it has already been setup. I added a third commit to my PR #63 which 100% fixes also this issue on iOS 15, iOS 16 and iOS 17.

elbelga commented 9 months ago

I believe it solves the crash, but focusstate doesnt work properly, doesnt focus in the correct field.

In the example above, when screen appears focus on password and it's not correct.

shaps80 commented 9 months ago

Thanks @shaps80 for the pointer to this issue. I could reproduce it on iOS 15, 16 and 17 and yes you are right, the issue is very similar to #62. Also here the delegate dance happened multiple times which created a cycle in the delegate chain. I could fix it in the same way as in the other two cases by not touching the delegate when it has already been setup. I added a third commit to my PR #63 which 100% fixes also this issue on iOS 15, iOS 16 and iOS 17.

Perfect, I merged already :)