Hi, 大家好乳幸,我是姜友華。上一節(jié)我們實現(xiàn)了標簽視圖钧椰,這一節(jié)我們來實現(xiàn)分割視圖粹断。
在UIKit里是有類似的SplitView
的,但在SwiftUI里我沒有找到嫡霞,所以需要自己來實現(xiàn)瓶埋。你也可以通過封裝`NSSplitViewController 和NSSplitView來實現(xiàn),這個也留到以后去講。
這里對分割視圖的設(shè)計比較簡單:首先是接收一組視圖养筒,按單一方向排列曾撤,中間有分割符隔開即可;其次是實現(xiàn)通過拖動割符隔改變視圖的大小晕粪。
一挤悉、排列的設(shè)計。
排列的處理比較簡單巫湘,接收數(shù)組按水平或垂直排列装悲,各區(qū)域的大小按百分比來處理即可。
先看代碼和顯示效果尚氛。
@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])
}
}
這個比較好理解,按水平或垂直排列視圖奈懒。水平的按寬設(shè)置占比奠涌,垂直的按高設(shè)置占比。這里又用到了
GeometryReader
磷杏,用來獲取布局的尺寸溜畅。
需要說明的是,視圖寬高的計算return (width - 10 ) * ratios[index]
极祸,都是減去一個常量再按占比算慈格,這個常量是分割線的寬。
二遥金、實現(xiàn)分割線的拖動浴捆。
分割線的拖動只需要按水平或垂直方向去處理。拖動時動態(tài)修改分割線的位置和視圖的大小稿械。
直接看代碼选泻。
struct SplitView: View {
......
// 分割線。
func dividerView(_ index: Int, _ size: CGSize) -> some View {
Divider()
.frame(width:1)
.onHover { inside in
// 鼠標風絡(luò)美莫。
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
}
}
}
拖動的處理分兩步:
- 首先是添加分割線的拖曳事件嫉拐,這是一種固定處理,即添加
DragGesture
對象魁兼。 - 其次是處理
onChanged
事件椭岩,這里的處理是,向移動的方向遂個處理有可能被縮小的視圖璃赡,保證每個視圖能以最小值顯示。處理完成后再處理影響放大的那一個即臨近分割線并與移動方向相反的那個献雅。
好碉考,這個也就這些,我是姜友華挺身,下次見