概要
iOS において、左にスライドすると、右から隠れたボタンが現れる View の Swift UI での実装方法について紹介します。
実装方法
ユーザがボタンをドラッグするのに合わせ、 View の x 軸のオフセットを変更することで、このような View を実現することができます。
Xcode の Playground で実行可能なコードは以下のとおりです。
import SwiftUI
import PlaygroundSupport
struct ContentView: View {
var body: some View {
VStack(spacing: 0) {
SlideButtons()
.frame(width: 300, height: 40)
SlideButtons()
.frame(width: 300, height: 40)
SlideButtons()
.frame(width: 300, height: 40)
}
}
}
struct SlideButtons: View {
// The current x offset of the view.
@State private var xOffset = CGFloat.zero
// The default x offset when a drag ends.
@State private var defaultXOffset = CGFloat.zero
// Initialize it on `.onAppear()`.
@State private var hiddenButtonWidth: CGFloat = 0
@State private var hiddenButtonMaxWidth: CGFloat = 50
// Ratio of a hidden button width to the entire View.
private let buttonSizeRatio = 6
private var dragGesture: some Gesture {
DragGesture(minimumDistance: 30, coordinateSpace: .local)
.onChanged { value in
self.xOffset += value.translation.width
if self.hiddenButtonMaxWidth * -2 <= self.xOffset && self.xOffset <= 0 {
self.hiddenButtonWidth = min(self.xOffset / -2, self.hiddenButtonMaxWidth)
}
}
.onEnded { value in
withAnimation() {
if self.areHiddenButtonsShowed() {
if (self.xOffset - self.defaultXOffset) > self.getMinDistanceToChangeDefaultXOffset() {
self.hideButtons()
} else {
// Return to the current default position.
self.xOffset = self.defaultXOffset
// Reset the button size
self.hiddenButtonWidth = self.hiddenButtonMaxWidth
}
} else {
if (self.defaultXOffset - self.xOffset) > self.getMinDistanceToChangeDefaultXOffset() {
self.showHiddenButtons()
} else {
// Return to the current default position.
self.xOffset = self.defaultXOffset
// Reset the button size
self.hiddenButtonWidth = 0
}
}
}
}
}
var body: some View {
GeometryReader { geometry in
HStack(spacing: 0) {
// Main button
Rectangle()
.foregroundColor(.black)
.overlay {
HStack {
Text("Read a book")
.foregroundColor(.white)
.padding()
Spacer()
}
}
.frame(width: geometry.size.width,
height: geometry.size.height)
.gesture(
self.dragGesture
)
.onTapGesture {
withAnimation() {
if self.areHiddenButtonsShowed() {
self.hideButtons()
} else {
self.showHiddenButtons()
}
}
}
// 1st hidden button
Rectangle()
.frame(width: self.hiddenButtonWidth, height: geometry.size.height)
.foregroundColor(Color.yellow)
.overlay {
Text("Skip")
.foregroundColor(.white)
}
// 2nd hidden button
Rectangle()
.frame(width: self.hiddenButtonWidth, height: geometry.size.height)
.foregroundColor(Color.red)
.overlay {
Text("Done")
.foregroundColor(.white)
}
}
.onAppear() {
self.hiddenButtonWidth = 0
self.hiddenButtonMaxWidth = geometry.size.width / CGFloat(self.buttonSizeRatio)
self.hideButtons()
}
.frame(width: geometry.size.width, height: geometry.size.height, alignment: .leading)
.offset(x: self.xOffset)
}
}
private func areHiddenButtonsShowed() -> Bool {
return self.defaultXOffset == self.getXOffsetToShowHiddenButtons()
}
private func hideButtons() {
self.hiddenButtonWidth = 0
self.xOffset = CGFloat.zero
self.defaultXOffset = .zero
}
private func getMinDistanceToChangeDefaultXOffset() -> CGFloat {
return self.hiddenButtonMaxWidth / 2
}
private func getXOffsetToShowHiddenButtons() -> CGFloat {
return (self.hiddenButtonMaxWidth * 2) * -1
}
private func showHiddenButtons() {
self.hiddenButtonWidth = self.hiddenButtonMaxWidth
self.xOffset = self.getXOffsetToShowHiddenButtons()
self.defaultXOffset = self.getXOffsetToShowHiddenButtons()
}
}
PlaygroundPage.current.liveView = UIHostingController(rootView: ContentView())