How to automatically add dividers between list elements in SwiftUI?

Viewed 680

I'm trying to replicate the some of the behavior of List. I specifically want to add dividers between all elements. My current code looks like this

Customlist {
  Text("...")
  Divider()
  Text("...")
  Divider()
  Text("...")
}

I want to remove the dividers and just provide the text nodes, but I have no idea how to inject the dividers automatically in Customlist. So the usage I want looks like this:

Customlist {
  Text("...")
  Text("...")
  Text("...")
}

I assume Customlist would have to look something like this (but I don't know how to implement body):

Customlist<Content: View>: View {
  var content: Content

  init(@ViewBuilder _ b: () -> Content) {
    self.content = b()
  }

  var body: some View {
    // Something using self.content here
  }
}
2 Answers
  1. Row send rowID(using PreferenceKey) to list.
  2. List get all rows's ids, and send the last id to row.
  3. row check self'id is equal to last row id.
/// store all row ids.
fileprivate struct RowsPreferenceKey: PreferenceKey {
    static var defaultValue: [Namespace.ID] = []
    static func reduce(value: inout [Namespace.ID], nextValue: () -> [Namespace.ID]) {
        value.append(contentsOf: nextValue())
    }
}

extension EnvironmentValues {
    
    fileprivate struct LastRowKey: EnvironmentKey {
        static var defaultValue: Namespace.ID? = nil
    }
    
    /// identity this row id, is last item in list
    var lastRowID: Namespace.ID? {
        get { self[LastRowKey.self] }
        set { self[LastRowKey].self = newValue }
    }
}

struct Row<Content: View>: View {
    
    @Environment(\.lastRowID) var lastRowID
    @Namespace var id
    
    var content: Content
    
    init(@ViewBuilder content: () -> Content) {
        self.content = content()
    }
    
    var body: some View {
        VStack {
            content
            // check self is last row
            if lastRowID != id {
                Divider()
            }
         // send row id to list.
        }.preference(key: RowsPreferenceKey.self, value: [id])
    }
    
}


struct DividerList<Content: View>: View {
    
    var content: Content
    
    init(@ViewBuilder content: () -> Content) {
        self.content = content()
    }
    
    @State var lastRowID: Namespace.ID?
    
    var body: some View {
        VStack { content }
        .onPreferenceChange(RowsPreferenceKey.self, perform: { value in
            self.lastRowID = value.last
        })
        /// send row id to Childs
        .environment(\.lastRowID, lastRowID)
    }
    
}

struct DividerList_Previews: PreviewProvider {
    static var previews: some View {
            DividerList {
                Row {
                    Text("Hello")
                }
                
                Row {
                    Text("World")
                }
                
                Row {
                    Text("!!!")
                }
            }
    }
}


preview

import SwiftUI
struct ParentView: View  {
    let array = [1,2,3,4,5]
    var body: some View {
        DividedList{
            VStack{
                //The next init for List would implement something like this
                ForEach(array, id: \.self) { elem in
                    VStack{
                        Text(elem.description)
                        
                        if elem.description != array.last?.description{
                            Divider()
                        }
                    }
                }
            }
        }
    }
}
struct DividedList<Content: View>: View {
    
    
    var content: Content
    //This is just the first init for the SwiftUI.List if you want the rest you have to implement them individually 
      //https://developer.apple.com/documentation/swiftui/list
      init(@ViewBuilder _ b: () -> Content) {
        self.content = b()
      }
    
    var body: some View {
        content
    }
}

struct DividedList_Previews: PreviewProvider {
    static var previews: some View {
        //DividedList({Text("test")})
        ParentView()
    }
}
Related