Skip to content

Commit

Permalink
Bugfix: Removes implicit animations
Browse files Browse the repository at this point in the history
* Adding explicit transition support

* Adding example for animation content independent of the main sheet

* Adding support for keyboard show / hide animations

* Improving animation example

* Removing unnecessary async from example
  • Loading branch information
eliottrobson committed Sep 23, 2020
1 parent 423eb88 commit 9364652
Show file tree
Hide file tree
Showing 5 changed files with 154 additions and 56 deletions.
4 changes: 4 additions & 0 deletions Example/PartialSheetExample.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
3B9841FC24E880870052A996 /* PickerExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B9841FA24E880870052A996 /* PickerExample.swift */; };
3B9841FD24E880870052A996 /* DatePickerExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B9841FB24E880870052A996 /* DatePickerExample.swift */; };
3BF874DF24E6F4DE004F4550 /* BlurredSheetExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BF874DE24E6F4DE004F4550 /* BlurredSheetExample.swift */; };
9C721F222519347C007E46D4 /* AnimationContentExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C721F212519347C007E46D4 /* AnimationContentExample.swift */; };
F8E410DE25015F710064D3A6 /* ViewModifierExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8E410DD25015F710064D3A6 /* ViewModifierExample.swift */; };
/* End PBXBuildFile section */

Expand All @@ -52,6 +53,7 @@
3B9841FA24E880870052A996 /* PickerExample.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PickerExample.swift; sourceTree = "<group>"; };
3B9841FB24E880870052A996 /* DatePickerExample.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DatePickerExample.swift; sourceTree = "<group>"; };
3BF874DE24E6F4DE004F4550 /* BlurredSheetExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlurredSheetExample.swift; sourceTree = "<group>"; };
9C721F212519347C007E46D4 /* AnimationContentExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnimationContentExample.swift; sourceTree = "<group>"; };
F8E410DD25015F710064D3A6 /* ViewModifierExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewModifierExample.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

Expand All @@ -77,6 +79,7 @@
0174F416245962B80053C454 /* PushNavigationExample.swift */,
3BF874DE24E6F4DE004F4550 /* BlurredSheetExample.swift */,
F8E410DD25015F710064D3A6 /* ViewModifierExample.swift */,
9C721F212519347C007E46D4 /* AnimationContentExample.swift */,
);
path = Examples;
sourceTree = "<group>";
Expand Down Expand Up @@ -212,6 +215,7 @@
0174F417245962B80053C454 /* PushNavigationExample.swift in Sources */,
3BF874DF24E6F4DE004F4550 /* BlurredSheetExample.swift in Sources */,
01A013962458E4C000D0F5DD /* TextfieldExample.swift in Sources */,
9C721F222519347C007E46D4 /* AnimationContentExample.swift in Sources */,
01A013942458E4A900D0F5DD /* NormalExample.swift in Sources */,
1F177A6E23E1ECC3006F59D0 /* AppDelegate.swift in Sources */,
01477D8B2458E928007AE720 /* PartialSheetManager.swift in Sources */,
Expand Down
14 changes: 6 additions & 8 deletions Example/PartialSheetExample/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ struct ContentView: View {
Text("""
Hi, this is the Partial Sheet modifier.
On iPhone devices it allows you to dispaly a totally custom sheet with a relative height based on his content.
On iPhone devices it allows you to display a totally custom sheet with a relative height based on its content.
In this way the sheet will cover the screen only for the space it will need.
On iPad and Mac devices it will present a normal .sheet view.
Expand All @@ -31,27 +31,21 @@ struct ContentView: View {
.padding()

List {

NavigationLink(
destination: NormalExample(),
label: {Text("Normal Example")

})
NavigationLink(
destination: TextfieldExample(),
label: {
Text("Textfield Example")

label: {Text("Textfield Example")
})
NavigationLink(
destination: ListExample(),
label: {Text("List Example")

})
NavigationLink(
destination: PushNavigationExample(),
label: {Text("Push Navigation Example")

})
NavigationLink(
destination: DatePickerExample(),
Expand All @@ -69,6 +63,10 @@ struct ContentView: View {
destination: ViewModifierExample(),
label: {Text("ViewModifier Example")
})
NavigationLink(
destination: AnimationContentExample(),
label: {Text("AnimationContent Example")
})
}
Spacer()
Spacer()
Expand Down
78 changes: 78 additions & 0 deletions Example/PartialSheetExample/Examples/AnimationContentExample.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
//
// AnimationContentExample.swift
// PartialSheetExample
//
// Created by Eliott Robson on 21/09/2020.
// Copyright © 2020 Swift. All rights reserved.
//

import SwiftUI

struct AnimationContentExample: View {
@EnvironmentObject var partialSheet: PartialSheetManager

var body: some View {
VStack {
Button(
action: {
self.partialSheet.showPartialSheet {
AnimationSheetView()
}
},
label: {
Text("Show Partial Sheet")
}
)
}
}
}

struct AnimationSheetView: View {

@State private var explicitScale: CGFloat = 1

@State private var implicitScale: CGFloat = 1

@State private var noScale: CGFloat = 1

var body: some View {
VStack {
Text("Tap to animate explicitly")
.padding()
.background(Color.green)
.cornerRadius(5)
.scaleEffect(explicitScale)
.onTapGesture {
withAnimation {
explicitScale = CGFloat.random(in: 0.5..<1.5)
}
}

Text("Tap to animate implicitly")
.padding()
.background(Color.orange)
.cornerRadius(5)
.scaleEffect(implicitScale)
.animation(.default)
.onTapGesture {
implicitScale = CGFloat.random(in: 0.5..<1.5)
}

Text("Tap to change with no animation")
.padding()
.background(Color.red)
.cornerRadius(5)
.scaleEffect(noScale)
.onTapGesture {
noScale = CGFloat.random(in: 0.5..<1.5)
}
}
}
}

struct AnimationContentExample_Previews: PreviewProvider {
static var previews: some View {
AnimationContentExample()
.environmentObject(PartialSheetManager())
}
}
10 changes: 8 additions & 2 deletions Sources/PartialSheet/PartialSheetManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,11 @@ public class PartialSheetManager: ObservableObject {
public func showPartialSheet<T>(_ onDismiss: (() -> Void)? = nil, @ViewBuilder content: @escaping () -> T) where T: View {
self.content = AnyView(content())
self.onDismiss = onDismiss
self.isPresented = true
DispatchQueue.main.async {
withAnimation {
self.isPresented = true
}
}
}

/**
Expand All @@ -68,7 +72,9 @@ public class PartialSheetManager: ObservableObject {
self.onDismiss = onDismiss
}
if let isPresented = isPresented {
self.isPresented = isPresented
withAnimation {
self.isPresented = isPresented
}
}
}

Expand Down
104 changes: 58 additions & 46 deletions Sources/PartialSheet/PartialSheetViewModifier.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,16 @@ struct PartialSheet: ViewModifier {

/// The rect containing the presenter
@State private var presenterContentRect: CGRect = .zero


/// The rect containing the sheet content
@State private var sheetContentRect: CGRect = .zero

/// The offset for keyboard height
@State private var offset: CGFloat = 0

/// The offset for the drag gesture
@State private var dragOffset: CGFloat = 0

/// The point for the top anchor
private var topAnchor: CGFloat {
return max(presenterContentRect.height +
Expand Down Expand Up @@ -60,19 +62,16 @@ struct PartialSheet: ViewModifier {
private var sheetPosition: CGFloat {
if self.manager.isPresented {
let topInset = UIApplication.shared.windows.first?.safeAreaInsets.top ?? 20.0 // 20.0 = To make sure we dont go under statusbar on screens without safe area inset
let position = self.topAnchor + self.dragState.translation.height - self.offset
let position = self.topAnchor + self.dragOffset - self.offset
if position < topInset {
return topInset
}

return position
} else {
return self.bottomAnchor - self.dragState.translation.height
return self.bottomAnchor - self.dragOffset
}
}

/// The Gesture State for the drag gesture
@GestureState private var dragState = DragState.inactive

/// Background of sheet
private var background: AnyView {
Expand Down Expand Up @@ -216,7 +215,6 @@ extension PartialSheet {
Color.clear.preference(key: SheetPreferenceKey.self, value: [PreferenceData(bounds: proxy.frame(in: .global))])
}
)
.animation(nil)
}
Spacer()
}
Expand All @@ -228,8 +226,6 @@ extension PartialSheet {
.cornerRadius(style.cornerRadius)
.shadow(color: Color(.sRGBLinear, white: 0, opacity: 0.13), radius: 10.0)
.offset(y: self.sheetPosition)
.animation(self.dragState.isDragging ?
nil : .interpolatingSpring(stiffness: 300.0, damping: 30.0, initialVelocity: 10.0))
.gesture(drag)
}
}
Expand All @@ -240,56 +236,68 @@ extension PartialSheet {
extension PartialSheet {

/// Create a new **DragGesture** with *updating* and *onEndend* func
private func dragGesture() -> _EndedGesture<GestureStateGesture<DragGesture, DragState>> {
private func dragGesture() -> _EndedGesture<_ChangedGesture<DragGesture>> {
DragGesture(minimumDistance: 30, coordinateSpace: .local)
.updating($dragState) { drag, state, _ in
self.dismissKeyboard()
let yOffset = drag.translation.height
let threshold = CGFloat(-50)
let stiffness = CGFloat(0.3)
if yOffset > threshold {
state = .dragging(translation: drag.translation)
} else if
// if above threshold and belove ScreenHeight make it elastic
-yOffset + self.sheetContentRect.height <
UIScreen.main.bounds.height + self.handlerSectionHeight
{
let distance = yOffset - threshold
let translationHeight = threshold + (distance * stiffness)
state = .dragging(translation: CGSize(width: drag.translation.width, height: translationHeight))
}
.onChanged(onDragChanged)
.onEnded(onDragEnded)
}

private func onDragChanged(drag: DragGesture.Value) {
self.dismissKeyboard()
let yOffset = drag.translation.height
let threshold = CGFloat(-50)
let stiffness = CGFloat(0.3)
if yOffset > threshold {
dragOffset = drag.translation.height
} else if
// if above threshold and belove ScreenHeight make it elastic
-yOffset + self.sheetContentRect.height <
UIScreen.main.bounds.height + self.handlerSectionHeight
{
let distance = yOffset - threshold
let translationHeight = threshold + (distance * stiffness)
dragOffset = translationHeight
}
.onEnded(onDragEnded)
}

/// The method called when the drag ends. It moves the sheet in the correct position based on the last drag gesture
private func onDragEnded(drag: DragGesture.Value) {
/// The drag direction
let verticalDirection = drag.predictedEndLocation.y - drag.location.y
/// The current sheet position
let cardTopEdgeLocation = topAnchor + drag.translation.height

// Get the closest anchor point based on the current position of the sheet
let closestPosition: CGFloat

if (cardTopEdgeLocation - topAnchor) < (bottomAnchor - cardTopEdgeLocation) {
closestPosition = topAnchor
} else {
closestPosition = bottomAnchor
}

// Set the correct anchor point based on the vertical direction of the drag
if verticalDirection > 1 {
DispatchQueue.main.async {
self.manager.isPresented = false
self.manager.onDismiss?()
withAnimation(.interpolatingSpring(stiffness: 300.0, damping: 30.0, initialVelocity: 10.0)) {
dragOffset = 0
self.manager.isPresented = false
self.manager.onDismiss?()
}
}
} else if verticalDirection < 0 {
self.manager.isPresented = true
withAnimation {
dragOffset = 0
self.manager.isPresented = true
}
} else {
self.manager.isPresented = (closestPosition == topAnchor)
if !manager.isPresented {
manager.onDismiss?()
/// The current sheet position
let cardTopEdgeLocation = topAnchor + drag.translation.height

// Get the closest anchor point based on the current position of the sheet
let closestPosition: CGFloat

if (cardTopEdgeLocation - topAnchor) < (bottomAnchor - cardTopEdgeLocation) {
closestPosition = topAnchor
} else {
closestPosition = bottomAnchor
}

withAnimation {
dragOffset = 0
self.manager.isPresented = (closestPosition == topAnchor)
if !manager.isPresented {
manager.onDismiss?()
}
}
}
}
Expand All @@ -304,14 +312,18 @@ extension PartialSheet {
if let rect: CGRect = notification.userInfo![endFrame] as? CGRect {
let height = rect.height
let bottomInset = UIApplication.shared.windows.first?.safeAreaInsets.bottom
self.offset = height - (bottomInset ?? 0)
withAnimation(.interpolatingSpring(stiffness: 300.0, damping: 30.0, initialVelocity: 10.0)) {
self.offset = height - (bottomInset ?? 0)
}
}
}

/// Remove the keyboard offset
private func keyboardHide(notification: Notification) {
DispatchQueue.main.async {
self.offset = 0
withAnimation(.interpolatingSpring(stiffness: 300.0, damping: 30.0, initialVelocity: 10.0)) {
self.offset = 0
}
}
}

Expand Down

0 comments on commit 9364652

Please sign in to comment.