Search code examples
swiftuistateinitializer

'self' used before all stored properties are initialized error - @State - extension - second file


I want to decouple some views. For this case I created an initializer for a view with a custom argument in an extension in a second file. Unfortunately I get this message: "'self' used before all stored properties are initialized".

If I put view and extension in the same file, it works. But in this case, the view file needs to know the custom argument. This may not be needed in another project.

For simplicity, I have created a sample code that shows the problem. How can I initialize the @State property name in the extension initializer if the extension is in another file?


//  ListCell.swift

import SwiftUI

struct ListCell: View {
    @State var name: String
    var count: Int

    var body: some View {
        HStack {
            Text(name)
            Text(": \(count)")
        }
    }
}

//  ListCell+Ext.swift

import SwiftUI

extension ListCell {
    init(name: String) {
        self.name = name
        count = 0
    }
}

Solution

  • The problem is that @State var name: String is actually sugar for a private value _name with some extra logic. You are trying to set String to the value that is exposed from the state (which is State<String>). If you will try to set _name in the extension the compiler will complain that you can't access this private value from the extension.

    Seems like you can create a convenience init in your extension, while keeping the default one, and just using it:

    //  ListCell.swift
    
    struct ListCell: View {
        @State var name: String
        var count: Int
    
        var body: some View {
            HStack {
                Text(name)
                Text(": \(count)")
            }
        }
    }
    
    //  ListCell+Ext.swift
    
    import SwiftUI
    
    extension ListCell {
        init(name: String) {
            self.init(name: name, count: 0)
        }
    }
    

    Note that Swift docs do say that a default struct initializer is only generated when there isn't a custom one ("Structure types automatically receive a memberwise initializer if they don’t define any of their own custom initializers" - under Memberwise Initializers) but seems like when using an extension like in your case the default init is still generated, and you can use it instead of creating one...

    By the way, in general, @State should be used when your view owns and creates the value, if it is passed from above, it's likely that you should actually use @Binding instead.