简体   繁体   中英

Using @FocusState on a TextField causing memory leaks

I am learning memory and ARC since a while and managed to use the Leaks instrument more often in order to produce quality code. Having that said, please consider my lack of experience in this zone.

Problem: I built a parent view A that presents a view B .

B contains a login form built using a TextField , a SecureField and a Button . I also have a @FocusState private var isFocused: Bool property that helps me hide the keyboard in order to bypass the "AttributeGraph: cycle detected" console error that shows up once I disable the textfield on button press having the keyboard on screen. ( Get `AttributeGraph: cycle detected` error when changing disabled state of text field )

I noticed that when I use the @FocusState property, once I dismiss B , the Leaks instrument detects two "Malloc 32 Bytes" leaks just like in the picture below.

If I don't use the @FocusState property, the leaks will no longer show up. Am I doing something wrong or is this some kind of bug / false positive from Swift?

在此处输入图像描述

This view is partially extracted from my file so it doesn't have all it's properties and methods here .

struct AuthenticationLoginView: View {
       
    @StateObject var viewModel = AuthenticationLoginViewModel()
    
    @FocusState private var isFocused: Bool
    
    var body: some View {
            VStack {
                TextField(text: $viewModel.username) {
                    Text("placeholder.")
                }
                .tag(AuthenticationLoginField.username)
                .textInputAutocapitalization(.never)
                .focused($isFocused)
                .disabled(viewModel.isLoggingIn)
                SecureField(text: $viewModel.password) {
                    Text("Password")
                }
//                .focused($isFocused)
                .disabled(viewModel.isLoggingIn)
                .tag(AuthenticationLoginField.password)
            }
    }
}

Without more code it is hard to tell what else you are doing that may have caused the leak.

My gut is that having a single focus boolean when you have two edit fields is an anti-pattern compared to Apple's way . When something is evolving like SwiftUI, try to follow their example styles more closely. Use a boolean only when there's just one focusable field.

This similar Hacking with Swift sample shows using an optional and changing the focus as fields submitted.

    @FocusState private var focusedField: FocusedField?
    @State private var username = "Anonymous"
    @State private var password = "sekrit"

    var body: some View {
        VStack {
            TextField("Enter your username", text: $username)
                .focused($focusedField, equals: .username)

            SecureField("Enter your password", text: $password)
                .focused($focusedField, equals: .password)
        }
        .onSubmit {
            if focusedField == .username {
                focusedField = .password
            } else {
                focusedField = nil
            }
        }

I found this problem. In my case, it caused a massive retain loop which fed all the way back to coordinator with a bunch of objects.

Just showing a TextField with focus (either the bool way, or the enum way) caused a retain.

My fix was to use the introspect library and just set first responder manually

@State private var textField:UITextField?
@State private var secureTextField:UITextField?


var body: some View {
    ZStack {
        TextField(prompt, text: $text)
            .opacity(isSecure ? 0 : 1)
            .introspectTextField { field in
                textField = field
            }
        
        SecureField(prompt,text: $text)
            .opacity(isSecure ? 1 : 0)
            .introspectTextField { field in
                secureTextField = field
            }
    }
    .animation(.easeInOut, value: isSecure)
    .overlay(alignment: .trailing) {
        Button {
            isSecure.toggle()
            setFocus()
        } label: {
            Image(isSecure ? "eyeClose" : "eyeOpen")
        }
        .opacity(canBeSecure ? 1 : 0)
    }
    .onAppear {
        DispatchQueue.main.async {
            setFocus()
        }
        
    }

    
}

func setFocus() {
    
    if isSecure {
        secureTextField?.becomeFirstResponder()
    }
    else {
        textField?.becomeFirstResponder()
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM