stevengharris / SplitView

A flexible way to split SwiftUI views with a draggable splitter
MIT License
146 stars 17 forks source link

Nested views ad infinitum #11

Closed cyrilzakka closed 1 year ago

cyrilzakka commented 1 year ago

Hello,

Hope you're well! This is a great library! I was wondering if it was possible to use it to create nested views continuously without predefining the layout. So given an initial view, the user clicks a button to split it in half. Given the two new views, the user can click a same button to split it etc.

Thanks!

stevengharris commented 1 year ago

It's an interesting problem! Without reviewing the more specific issue you raised later, I put this together as an attempt to capture what you specified. If you use a DynamicSplit(), it shows a "Split" button that increments the nSplits state to redraw the view. The starting NestedHSplit recursively creates alternating NestedVSplits and NestedHSplits until the passed-in nSplits is 0. If you need to retain the split state, I would use a property based on the value of nSplits at each instantiation.

struct DynamicSplit: View {
    @State var nSplits: Int = 0
    var body: some View {
        VStack {
            let primordialView = Color.red
            let newView = Color.green
            Button("Split") { nSplits += 1 }
            if nSplits > 0 {
                NestedHSplit(nSplits: nSplits, primary: { primordialView }, secondary: { newView })
            } else {
                primordialView
            }
        }
    }

}

struct NestedHSplit<P: View, S : View>: View {
    let nSplits: Int
    var primary: P
    var secondary: S

    var body: some View {
        if nSplits == 0 {
            primary
        } else {
            primary.hSplit { NestedVSplit(nSplits: nSplits - 1, primary: { secondary }, secondary: { primary } ) }
        }
    }

    init(nSplits: Int, @ViewBuilder primary: @escaping ()->P, @ViewBuilder secondary: @escaping ()->S ) {
        self.nSplits = nSplits
        self.primary = primary()
        self.secondary = secondary()
    }
}

struct NestedVSplit<P: View, S : View>: View {
    let nSplits: Int
    var primary: P
    var secondary: S

    var body: some View {
        if nSplits == 0 {
            primary
        } else {
            primary.vSplit { NestedHSplit(nSplits: nSplits - 1, primary: { secondary }, secondary: { primary } ) }
        }
    }

    init(nSplits: Int, @ViewBuilder primary: @escaping ()->P, @ViewBuilder secondary: @escaping ()->S ) {
        self.nSplits = nSplits
        self.primary = primary()
        self.secondary = secondary()
    }
}
cyrilzakka commented 1 year ago

That works perfectly, thank you! Will modify it to behavior closer to Xcode's split pane behavior. Also greetings from Palo Alto!

stevengharris commented 1 year ago

Feel free to re-open if needed.