Skip to content

Commit

Permalink
Smooth out orientation change animations
Browse files Browse the repository at this point in the history
  • Loading branch information
aheze committed Jan 18, 2022
1 parent 3afa53a commit 268e05a
Show file tree
Hide file tree
Showing 7 changed files with 40 additions and 38 deletions.
2 changes: 1 addition & 1 deletion Sources/Popover+Lifecycle.swift
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ public extension Popover {

withTransaction(transaction) {
/// Temporarily use the same size for a smooth animation.
newPopover.setSize(oldContext.size)
newPopover.updateFrame(with: oldContext.size)

/// Replace the old popover with the new popover.
model.popovers[oldPopoverIndex] = newPopover
Expand Down
10 changes: 5 additions & 5 deletions Sources/Popover.swift
Original file line number Diff line number Diff line change
Expand Up @@ -477,16 +477,16 @@ public extension Popover {
}
}

/// Set the popover's size from SwiftUI. Also update the frame.
func setSize(_ size: CGSize?) {
/// Updates the popover's frame using its size.
func updateFrame(with size: CGSize?) {
let frame = calculateFrame(from: size)
context.size = size
let frame = getFrame(from: size)
context.staticFrame = frame
context.frame = frame
}

/// Calculate the popover's frame based on it's size and position.
func getFrame(from size: CGSize?) -> CGRect {
/// Calculate the popover's frame based on its size and position.
func calculateFrame(from size: CGSize?) -> CGRect {
guard let window = context.presentedPopoverContainer?.window else { return .zero }

switch attributes.position {
Expand Down
7 changes: 3 additions & 4 deletions Sources/PopoverContainerView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -64,17 +64,16 @@ struct PopoverContainerView: View {
)

/// Read the popover's size in the view.
.sizeReader { size in
print("size: \(size).. \(popover.context.transaction)")
.sizeReader(transaction: popover.context.transaction) { size in
if let transaction = popover.context.transaction {
/// When `popover.context.size` is nil, the popover was just presented.
if popover.context.size == nil {
popover.setSize(size)
popover.updateFrame(with: size)
popoverModel.refresh(with: transaction)
} else {
/// Otherwise, the popover is *replacing* a previous popover, so animate it.
withTransaction(transaction) {
popover.setSize(size)
popover.updateFrame(with: size)
popoverModel.refresh(with: transaction)
}
}
Expand Down
31 changes: 9 additions & 22 deletions Sources/PopoverModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -83,32 +83,19 @@ class PopoverModel: ObservableObject {
This is called when the device rotates or has a bounds change.
*/
func updateFrames() {
var popoversToUpdate = [Popover]()

/**
First, update all popovers anyway.
For some reason, relative positioning + `.center` doesn't need the rotation animation to complete before having a size change.
*/
for popover in popovers {
popover.setSize(popover.context.size)

if
case let .relative(popoverAnchors) = popover.attributes.position,
popoverAnchors == [.center]
{
/// For some reason, relative positioning + `.center` doesn't need the rotation animation to complete before having a size change.
popover.setSize(popover.context.size)
update()
} else {
popoversToUpdate.append(popover)
}
popover.updateFrame(with: popover.context.size)
}
update()

/// Other popovers need to wait until the rotation has completed before updating.
/// Some other popovers need to wait until the rotation has completed before updating.
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
for popover in popoversToUpdate {
popover.setSize(popover.context.size)
}

withAnimation {
self.update()
}
self.refresh(with: Transaction(animation: .default))
}
}

Expand Down
2 changes: 1 addition & 1 deletion Sources/PopoverTemplates.swift
Original file line number Diff line number Diff line change
Expand Up @@ -568,7 +568,7 @@ public struct PopoverTemplates {
model.active = nil
}
)
.onDataChange(of: model.selected) { _, _ in
.onValueChange(of: model.selected) { _, _ in
if
let selected = model.selected,
selected == menuID.id
Expand Down
22 changes: 19 additions & 3 deletions Sources/PopoverUtilities.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,12 @@ public extension View {
)
}

/// Read a view's size. From https://stackoverflow.com/a/66822461/14351818
func sizeReader(size: @escaping (CGSize) -> Void) -> some View {
/**
Read a view's size. The closure is called whenever the size itself changes, or the transaction changes (in the event of a screen rotation.)
From https://stackoverflow.com/a/66822461/14351818
*/
func sizeReader(transaction: Transaction?, size: @escaping (CGSize) -> Void) -> some View {
return background(
GeometryReader { geometry in
Color.clear
Expand All @@ -53,12 +57,24 @@ public extension View {
size(newValue)
}
}
.onValueChange(of: transaction) { _, _ in
DispatchQueue.main.async {
size(geometry.size)
}
}
}
.hidden()
)
}
}

extension Transaction: Equatable {
public static func == (lhs: Transaction, rhs: Transaction) -> Bool {
lhs.animation == rhs.animation
}
}


struct ContentFrameReaderPreferenceKey: PreferenceKey {
static var defaultValue: CGRect { return CGRect() }
static func reduce(value: inout CGRect, nextValue: () -> CGRect) { value = nextValue() }
Expand Down Expand Up @@ -193,7 +209,7 @@ struct ChangeObserver<Content: View, Value: Equatable>: View {

public extension View {
/// Detect changes in bindings (fallback of `.onChange` for iOS 13+).
func onDataChange<Value: Equatable>(
func onValueChange<Value: Equatable>(
of value: Value,
perform action: @escaping (_ oldValue: Value, _ newValue: Value) -> Void
) -> some View {
Expand Down
4 changes: 2 additions & 2 deletions Sources/SwiftUI/Modifiers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ struct PopoverModifier: ViewModifier {
}

/// Detect a state change in `$present`.
.onDataChange(of: present) { _, newValue in
.onValueChange(of: present) { _, newValue in

/// `newValue` is true, so present the popover.
if newValue {
Expand Down Expand Up @@ -179,7 +179,7 @@ struct MultiPopoverModifier: ViewModifier {
}

/// `$selection` was changed, determine if the popover should be presented, animated, or dismissed.
.onDataChange(of: selection) { oldSelection, newSelection in
.onValueChange(of: selection) { oldSelection, newSelection in
let model = window.popoverModel

/// Save the frame in `selectionFrameTags` to provide `excludedFrames`.
Expand Down

0 comments on commit 268e05a

Please sign in to comment.