五、iWriter 分割视图的实现

2022-02-21  本文已影响0人  Jiangyouhua

Hi, 大家好,我是姜友华。上一节我们实现了标签视图,这一节我们来实现分割视图。

在UIKit里是有类似的SplitView的,但在SwiftUI里我没有找到,所以需要自己来实现。你也可以通过封装`NSSplitViewControllerNSSplitView来实现,这个也留到以后去讲。

这里对分割视图的设计比较简单:首先是接收一组视图,按单一方向排列,中间有分割符隔开即可;其次是实现通过拖动割符隔改变视图的大小。

一、排列的设计。

排列的处理比较简单,接收数组按水平或垂直排列,各区域的大小按百分比来处理即可。

先看代码和显示效果。

    @State var layouts: [AnyView] = []   // 需要排列的对象。
    @State var ratios: [CGFloat] = []
    @State var isHorizontal  = true  // 是否为水平排列。
    
    var body: some View {
        ZStack{
        GeometryReader{ geometry in
            if isHorizontal {
                HStack(spacing: 0) {
                    ForEach(Array(layouts.enumerated()), id: \.offset) { index, layout in
                        layout
                            .frame( width: frameWidth(index, geometry.size.width))
                        if index < layouts.count - 1 {
                            dividerView(index, geometry.size)
                        }
                    }
                }
            } else {
                VStack(spacing: 0) {
                    ForEach(Array(layouts.enumerated()), id: \.offset) { index, layout in
                        layout
                            .frame( height: frameHeight(index, geometry.size.width))
                        if index < layouts.count - 1 {
                            dividerView(index, geometry.size)
                        }
                    }
                }
            }
        }
        }
    }
    
    func frameWidth(_ index: Int, _ width: CGFloat) -> CGFloat {
        return (width - 10 ) * ratios[index]
    }
    
    func frameHeight(_ index: Int, _ height: CGFloat) -> CGFloat {
        return (height - 1) * ratios[index]
    }
    
    // 分割线。
    func dividerView(_ index: Int, _ size: CGSize) -> some View {
        ......
    }
    
    // 拖动分割线。
    func splitDrag(size: CGSize, current: Int) -> some Gesture {
       ......
            }
    }
}

struct SplitView_Previews: PreviewProvider {
    static var previews: some View {
        SplitView(layouts: [
            AnyView(Text("Window 1").background(.red)),
            AnyView(Text("Window 2").background(.red))
        ], ratios: [0.5, 0.5])
    }
}
效果
这个比较好理解,按水平或垂直排列视图。水平的按宽设置占比,垂直的按高设置占比。这里又用到了GeometryReader,用来获取布局的尺寸。

需要说明的是,视图宽高的计算return (width - 10 ) * ratios[index],都是减去一个常量再按占比算,这个常量是分割线的宽。

二、实现分割线的拖动。

分割线的拖动只需要按水平或垂直方向去处理。拖动时动态修改分割线的位置和视图的大小。
直接看代码。

struct SplitView: View {
......
    // 分割线。
    func dividerView(_ index: Int, _ size: CGSize) -> some View {
        Divider()
            .frame(width:1)
            .onHover { inside in
                // 鼠标风络。
                if inside {
                    if isHorizontal {
                        NSCursor.resizeLeftRight.push()
                    } else {
                        NSCursor.resizeUpDown.push()
                    }
                } else {
                    NSCursor.pop()
                }
            }
            .gesture(
                splitDrag(size: size ,current: index)
            )
    }
    
    // 拖动分割线。
    func splitDrag(size: CGSize, current: Int) -> some Gesture {
        DragGesture()
            .onChanged { value in
                // 分割线所有的百分比。
                let value = isHorizontal ? value.location.x : value.location.y
                let toStart = value < 0
                let offset = abs(value) / (isHorizontal ? size.width : size.height)
                let minSize = splitMinSize / (isHorizontal ? size.width : size.height)
                var result = offset
                var array: [CGFloat] = []
                ratios.forEach{ ratio in
                    array.append(ratio)
                }
                
                // 向左或上。
                if toStart {
                    for i in stride(from: current, through: 0, by: -1) {
                        if result < 0.001 {
                            break
                        }
                        // 足够。
                        if array[i] > result + minSize  {
                            array[i] -= result
                            result = 0
                            break
                        }
                        // 不够,留最小。
                        let value = array[i] - minSize
                        array[i] = minSize
                        result -= value
                    }
                    if offset > result + 0.001 {
                        array[current + 1] += (offset - result)
                    }
                    ratios = array
                    return
                }
                
                // 向右或下。
                for i in stride(from: current + 1, to: array.count, by: 1) {
                    if result < 0.001 {
                        break
                    }
                    // 足够。
                    if array[i] > result + minSize  {
                        
                        array[i] -= result
                        result = 0
                        break
                    }
                    // 不够,留最小。
                    let value = array[i] - minSize
                    array[i] = minSize
                    result -= value
                }
                if offset > result + 0.001 {
                    array[current] += (offset - result)
                }
                ratios = array
            }
    }
}

拖动的处理分两步:

好,这个也就这些,我是姜友华,下次见

上一篇下一篇

猜你喜欢

热点阅读