Search code examples
swiftuiswiftui-list

Where is the syntax of Apple documentation explained? (Specifically swiftUI documentation)


Where is an explanation of the syntax of the Apple's software? For example, when I look up 'List' for SwiftUI (cmd-shift-0 in Xcode) I see, among other alternatives:

init<Data, ID, RowContent>(Data, id: KeyPath<Data.Element, ID>, selection: Binding<Set<SelectionValue>>?, rowContent: (Data.Element)
-> RowContent)

Great! What do the angle brackets mean? What is the significance of "RowContent" vs "rowContent"? etc. What do I actually write to make a list? Where is an explanation of this syntax?

If this is industry standard (I don't think so) what standard? I don't need a totally noob explanation but I need something more to go on than I have.


Solution

  • 1. If you go to Apple's documentation for List, there's a bit more information on the init function in your question.

    Here it is formatted below to fit:

    // List declaration
    struct List<SelectionValue, Content>
      where 
         SelectionValue : Hashable, 
         Content : View
    
    
    // init declaration
    init<Data, ID, RowContent>(
      _ data: Data, 
      id: KeyPath<Data.Element, ID>, 
      selection: Binding<SelectionValue?>?, 
      @ViewBuilder rowContent: @escaping (Data.Element) -> RowContent
    ) where 
       Content == ForEach<Data, ID, HStack<RowContent>>, 
       Data : RandomAccessCollection, 
       ID : Hashable, 
       RowContent : View
    

    It's a bit of a mouthful, I agree.

    2. Angle brackets are used for Generics. This is a big topic that you need to understand about the language to be able to proceed.

    In a nutshell, a Generic is a placeholder for the eventual concrete type (String, Int, HStack, Foo, etc...) that will be used, which is defined by the user of this function.

    The generic placeholder type can be constrained to conform to a specific protocol or inherit from a certain class. This is done with the where clause.

    3. So, one by one:

    List has 2 placeholders: SelectionValue and Content (could have been named anything, like <T, U>):

    struct List<SelectionValue, Content>
    

    and these placeholders are constrained:

    where
       SelectionValue : Hashable, 
       Content : View
       
    

    They are constrained to conform to Hashable and to View respectively.

    So, basically, whenever you see Content in its function definitions, it means it could be any implementation of View. Likewise for SelectionValue.

    Moving on:

    init<Data, ID, RowContent>
    

    In addition to the placeholders that List declares, init is also generalized to around three additional placeholders: Data, ID, and RowContent, also constrained:

    where 
       Content == ForEach<Data, ID, HStack<RowContent>>, 
       Data : RandomAccessCollection, 
       ID : Hashable, 
       RowContent : View
    

    Content: has to be a ForEach (of a specific type). You actually shouldn't care about it, since Content isn't any of the parameter types of init

    Data: any type conforming to RandomAccessCollection, e.g. Array

    ID: any type conforming to Hashable

    RowContent: Any type conforming to View - basically any View

    4. Now, for the parameters:

    init<Data, ID, RowContent>(
      _ data: Data, 
      id: KeyPath<Data.Element, ID>, 
      selection: Binding<SelectionValue?>?, 
      @ViewBuilder rowContent: @escaping (Data.Element) -> RowContent
    )
    

    _ data: is an unlabeled parameter of type Data, which is (see above) something like an Array

    id: is a KeyPath (also, a topic of Swift that you should learn), but in a nutshell, an expression pointing to a property of an object. In this case, the object is an element of the Data (e.g. an Array), and the property is any ID (aka a Hashable).

    selection: is a Binding. Read more about bindings in SwiftUI, and more about property wrappers (a Binding is available via a @State property wrapper). But it basically enables you to pass your state variable to a view so that it could change it without owning the data. A binding goes hand-in-hand with a @State wrapped property, by accessing its projected value with a $ prefix. See example below.

    rowContent: is a closure that takes an element of said Data (e.g. an element of an Array) and returns a RowContent (aka some View). Don't worry about @ViewBuilder for now.

    5. Example:

    let strArray = ["One", "Two", "Three"]
    @State var selection: String? = 0
    
    // ...
    
    List(strArray, id: \.self, selection: $selection, rowContent: { str in
      return Text(str)
    })
    

    strArray: is an Array, so it satisfies the generic constrain of Data

    \.self: a key path expression, pointing to self (a String, which conforms to Hashable). This is only safe when the array is static and doesn't contain duplicates.

    selection: is bound to the state variable selection. $selection is a projected value of that variable, which SwiftUI conveniently made into a Binding type.

    rowContent: is a closure over each element of the array and returns a Text view.