Reputation: 35993
See this gif
What is happening on this gif is: the finger touches the white area on any point on the right part and drags to the left. As the finger drags, these 3 buttons zoom in and appear.
Assuming the buttons zoom from scale = 0
to scale = 1
, Does not matter if I release the finger when the scale is at any value bigger than 0. The buttons will zoom to scale 1 automatically.
NOTE: The animation is slow because I am sliding the finger slowly, but the animation follows the finger drag. If I drag left, buttons zoom in, if I drag right, buttons zoom out.
How do I do that with SwiftUI.
I have this code so far for the whole thing.
struct FileManagerPanelListItem: View {
var body: some View {
ZStack{
VStack {
ZStack {
Image("image")
.resizable()
.frame(width: 190, height: 190, alignment: .center)
HStack(alignment:.center){
FileManagerPanelButton("share", Color.white, Color.gray, {})
FileManagerPanelButton("duplicate", Color.white, Color.gray, {})
FileManagerPanelButton("delete", Color.white, Color.red, {})
}
.frame(maxWidth:.infinity)
}
Text("name")
.frame(width:170)
.background(Color.yellow)
.lineLimit(2)
.fixedSize(horizontal: false, vertical: true)
Text("notes")
.frame(width:170)
.background(Color.blue)
.lineLimit(2)
.fixedSize(horizontal: false, vertical: true)
}
}
.frame(maxWidth:240, maxHeight: 240)
}
}
struct FileManagerPanelListItem_Previews: PreviewProvider {
static var previews: some View {
FileManagerPanelListItem()
.previewDevice(PreviewDevice(rawValue: "iPhone 12 Pro Mini"))
}
}
struct FileManagerPanelButton: View {
typealias runOnSelectHandler = ()->Void
private var runOnSelect:runOnSelectHandler?
private var label:String
private var fgColor:Color
private var bgColor:Color
init(_ label: String,
_ fgColor:Color,
_ bgColor:Color,
_ runOnSelect: runOnSelectHandler? ) {
self.label = label
self.fgColor = fgColor
self.bgColor = bgColor
self.runOnSelect = runOnSelect
}
var body: some View {
Button(action: {
runOnSelect?()
}, label: {
Text(label)
.avenir(.ROMAN, size: 16)
.foregroundColor(fgColor)
.frame(height:60)
.frame(maxWidth:.infinity)
})
.frame(maxWidth:.infinity)
.background(bgColor)
.cornerRadius(10)
}
}
any ideas?
Upvotes: 1
Views: 116
Reputation: 88024
Something like this should work:
// view cannot be scaled to zero, so we take some small value near
private let minScale: CGFloat = 0.001
// relative distance from right side of view to open and from left side to close
private let effectiveDragSidePart: CGFloat = 0.1
struct FileManagerPanelListItem: View {
@State
var scale: CGFloat = minScale
@State
var opened = false
var body: some View {
ZStack{
VStack {
ZStack {
Image("profile")
.resizable()
.frame(width: viewWidth, height: 190, alignment: .center)
.gesture(
DragGesture()
.onChanged { value in
guard shouldProcessStartLocation(value.startLocation) else { return }
scale = translationToScale(value.translation)
}
.onEnded { value in
guard shouldProcessStartLocation(value.startLocation) else { return }
withAnimation(.spring()) {
if shouldRestore(value.predictedEndTranslation) {
scale = opened ? 1 : minScale
} else {
scale = opened ? minScale : 1
opened.toggle()
}
}
}
)
HStack(alignment:.center){
FileManagerPanelButton("share", Color.white, Color.gray, {})
.scaleEffect(scale, anchor: .center)
FileManagerPanelButton("duplicate", Color.white, Color.gray, {})
.scaleEffect(scale, anchor: .center)
FileManagerPanelButton("delete", Color.white, Color.red, {})
.scaleEffect(scale, anchor: .center)
}
.frame(maxWidth:.infinity)
}
}
}
.frame(maxWidth:240, maxHeight: 240)
}
private let viewWidth: CGFloat = 190
private func shouldRestore(_ predictedEndTranslation: CGSize) -> Bool {
if opened {
return translationToScale(predictedEndTranslation) == 1
} else {
return translationToScale(predictedEndTranslation) == minScale
}
}
private func shouldProcessStartLocation(_ startLocation: CGPoint) -> Bool {
if opened {
return startLocation.x < viewWidth * effectiveDragSidePart
} else {
return startLocation.x > viewWidth * (1 - effectiveDragSidePart)
}
}
private func translationToScale(_ translation: CGSize) -> CGFloat {
if opened {
return max(0.001, min(1, 1 - translation.width / viewWidth))
} else {
return max(0.001, -translation.width / viewWidth)
}
}
}
You can setup .spring()
if the default one is not springy enough
Upvotes: 1