skip to Main Content

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?

enter image description here

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)
            }
    }
}

2

Answers


  1. 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
                }
            }
    
    Login or Signup to reply.
  2. 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()
        }
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search