I am trying to create a View that acts as a split view, with an adjustable handle in the center of the two views.
Here's my current code:
struct ContentView: View {
@State private var gestureTranslation = CGSize.zero
@State private var prevTranslation = CGSize.zero
var body: some View {
VStack {
Rectangle()
.fill(Color.red)
.frame(height: (UIScreen.main.bounds.height / 2) + self.gestureTranslation.height)
RoundedRectangle(cornerRadius: 5)
.frame(width: 40, height: 3)
.foregroundColor(Color.gray)
.padding(2)
.gesture(DragGesture()
.onChanged({ value in
self.gestureTranslation = CGSize(width: value.translation.width + self.prevTranslation.width, height: value.translation.height + self.prevTranslation.height)
})
.onEnded({ value in
self.gestureTranslation = CGSize(width: value.translation.width + self.prevTranslation.width, height: value.translation.height + self.prevTranslation.height)
self.prevTranslation = self.gestureTranslation
})
)
Rectangle()
.fill(Color.green)
.frame(height: (UIScreen.main.bounds.height / 2) - self.gestureTranslation.height)
}
}
}
And what that code produces: [
This partially works, but when dragging the handle, it is very glitchy, and that it seems to require a lot of dragging to reach a certain point.
Please advise what went wrong. Thank you.
From what I have observed, the issue seems to be coming from the handle being repositioned while being dragged along. To counteract that I have set an inverse offset on the handle, so it stays in place. I have tried to cover up the persistent handle position as best as I can, by hiding it beneath the other views (zIndex).
I hope somebody else got a better solution to this question. For now, this is all that I have got:
import PlaygroundSupport
import SwiftUI
struct SplitView<PrimaryView: View, SecondaryView: View>: View {
// MARK: Props
@GestureState private var offset: CGFloat = 0
@State private var storedOffset: CGFloat = 0
let primaryView: PrimaryView
let secondaryView: SecondaryView
// MARK: Initilization
init(
@ViewBuilder top: @escaping () -> PrimaryView,
@ViewBuilder bottom: @escaping () -> SecondaryView)
{
self.primaryView = top()
self.secondaryView = bottom()
}
// MARK: Body
var body: some View {
GeometryReader { proxy in
VStack(spacing: 0) {
self.primaryView
.frame(height: (proxy.size.height / 2) + self.totalOffset)
.zIndex(1)
self.handle
.gesture(
DragGesture()
.updating(self.$offset, body: { value, state, _ in
state = value.translation.height
})
.onEnded { value in
self.storedOffset += value.translation.height
}
)
.offset(y: -self.offset)
.zIndex(0)
self.secondaryView.zIndex(1)
}
}
}
// MARK: Computed Props
var handle: some View {
RoundedRectangle(cornerRadius: 5)
.frame(width: 40, height: 3)
.foregroundColor(Color.gray)
.padding(2)
}
var totalOffset: CGFloat {
storedOffset + offset
}
}
// MARK: - Playground
let splitView = SplitView(top: {
Rectangle().foregroundColor(.red)
}, bottom: {
Rectangle().foregroundColor(.green)
})
PlaygroundPage.current.setLiveView(splitView)
Just paste the code inside XCode Playground / Swift Playgrounds
If you found a way to improve my code please let me know.