how to use hsplitview on swift

Viewed 521

I am writing a Mac app with a typical navigation pane on the left - and a content pane on the right. I want the pane to start off with the navigation pane being 1/4 of the width, and the content pane being 3/4 of the width, but I want you to be able to drag the size of the pane, ie - just like every other app ever.

so - I found HSplitview - which seem to be what I want - I do


     HSplitView() 
     {
         NavigationPane()
         ContentPane()
     }

and it sort of works. But really doesn't - the problems I need to fix:

  • when it starts, the navigation pane is tiny - more like 1/20 than the 1/4 I want
  • there's a strange spacing between the navigation pane and the content pane, to the left of the splitter
  • the splitter that you drag seems to be of zero width - I have to exactly align the cursor with the border of the content pane to allow it to drag
  • I want to save and then restore the position if the user drags it

so - any idea how I achieve what I want with the hsplitview? The weirdest thing is I don't seem to be able to find any documentation on it whatsoever - the official apple doc basically just says "yes, there is a class with that name."

1 Answers

it ended up being too hard - just wrote my own that does exactly what I want - here it is if anyone else wants it:

//
//  SplitView.swift
//  CoderExplorer
//
//  Created by Darren Oakey on 3/8/20.
//  Copyright © 2020 Darren Oakey. All rights reserved.
//


import SwiftUI

///
/// a splitter view that has a left and right pane, the splitter can be dragged or set and read programamatically
///
struct SplitView<Left:View, Right:View>: View
{
    ///
    /// provides the view for the left pane
    ///
    @State var left : () -> Left
    
    ///
    /// provides the view for the right pane
    ///
    @State var right : () -> Right
    
    ///
    /// how wide is the draggable section in between the two panes
    ///
    @State var splitterWidth = 10
    
    ///
    /// the x start coordinate of the splitter
    ///
    @State var splitterLocation = 200
    
    ///
    /// used for dragging - where we originally started - we have to keep our own, because the drag coordinates are relative to the location
    /// of the splitter, but we keep changing it while dragging
    ///
    @State var originalLocation = 0
    
    ///
    /// we use this to basically simulate a "dragStart" - as oddly enough swift only gives change and done
    ///
    @State var dragging = false
    
    ///
    /// if being dragged - the alteration to the current splitter location
    ///
    @State var offset : CGFloat = 0
    
    ///
    /// works out what the splitter location should be
    ///
    var splitterLocationComputed : Int 
    {
        if (dragging)
        {
            return originalLocation + Int( offset)
        }
        return splitterLocation
    }
    
    ///
    /// this moves the splitter based on how we've dragged
    ///
    func dragChange( gesture : DragGesture.Value )
    {
        if (!self.dragging)
        {
            self.originalLocation = self.splitterLocation
            self.dragging = true
        }
        self.offset = gesture.location.x + offset - gesture.startLocation.x
    }
    
    ///
    /// dragging is finished - commit the change to the splitter location
    ///
    func dragDone( gesture : DragGesture.Value )
    {
        self.dragging = false
        self.splitterLocation += Int(self.offset)
        self.offset = 0
    }
    
    ///
    /// the view is pretty simple - left pane, splitter, right pane
    ///
    var body: some View
    {
        GeometryReader()
        {
            geometry in
            HStack( spacing:0)
            {
                left().frame( width:CGFloat(self.splitterLocationComputed), height: geometry.size.height)
                Spacer().frame( width : CGFloat( splitterWidth), height : CGFloat( geometry.size.height))
                    .background(Color.blue).gesture( DragGesture().onChanged( dragChange)
                                                        .onEnded(dragDone ))
                right().frame(width: geometry.size.width - CGFloat( self.splitterWidth + self.splitterLocationComputed), height: geometry.size.height)
            }
        }
    }
}


///
/// provide a simple preview
///
struct SplitView_Previews: PreviewProvider
{
    static var previews: some View
    {
        SplitView( left: {Text("green").background(Color.green)},
                   right: {Text("red")}).frame(width:300, height:100)
    }
}


Related