Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support blueprint update animations in coordinator updates #274

Merged
merged 4 commits into from
Mar 10, 2021
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions BlueprintUILists/Sources/BlueprintHeaderFooterContent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,15 @@ public extension BlueprintHeaderFooterContent
views.content.element = self.elementRepresentation.wrapInBlueprintEnvironmentFrom(environment: info.environment)
views.background.element = self.background?.wrapInBlueprintEnvironmentFrom(environment: info.environment)
views.pressed.element = self.pressedBackground?.wrapInBlueprintEnvironmentFrom(environment: info.environment)

/// `BlueprintView` does not update its content until the next layout cycle.
/// Force that layout cycle within this method if we're updating an already on-screen
/// `ItemContent`, to ensure that we inherit any animation blocks we may be within.
if reason == .wasUpdated {
views.content.layoutIfNeeded()
views.background.layoutIfNeeded()
views.pressed.layoutIfNeeded()
}
}

static func createReusableContentView(frame: CGRect) -> ContentView {
Expand Down
9 changes: 9 additions & 0 deletions BlueprintUILists/Sources/BlueprintItemContent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,15 @@ public extension BlueprintItemContent
views.content.element = self.element(with: info).wrapInBlueprintEnvironmentFrom(environment: info.environment)
views.background.element = self.backgroundElement(with: info)?.wrapInBlueprintEnvironmentFrom(environment: info.environment)
views.selectedBackground.element = self.selectedBackgroundElement(with: info)?.wrapInBlueprintEnvironmentFrom(environment: info.environment)

/// `BlueprintView` does not update its content until the next layout cycle.
/// Force that layout cycle within this method if we're updating an already on-screen
/// `ItemContent`, to ensure that we inherit any animation blocks we may be within.
if reason == .wasUpdated {
views.content.layoutIfNeeded()
views.background.layoutIfNeeded()
views.selectedBackground.layoutIfNeeded()
}
}

/// Creates the `BlueprintView` used to render the content of the item.
Expand Down
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@

### Changed

- [Updates to `ItemContentCoordinator`](https://github.com/kyleve/Listable/pull/274) to properly support animations in Blueprint-backed rows. This change also generalizes the contained animation type to `ViewAnimation`, for use in both scrolling and content updates.

### Misc

# Past Releases
Expand Down
4 changes: 4 additions & 0 deletions Demo/Demo.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
0AA4D9C8248064A300CF95A5 /* ReorderingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AA4D9B7248064A300CF95A5 /* ReorderingViewController.swift */; };
0AA4D9C9248064A300CF95A5 /* CollectionViewBasicDemoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AA4D9B8248064A300CF95A5 /* CollectionViewBasicDemoViewController.swift */; };
0AC2A1962489F93E00779459 /* PagedViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AC2A1952489F93E00779459 /* PagedViewController.swift */; };
0AC839A525EEAD110055CEF5 /* OnTapItemAnimationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AC839A425EEAD110055CEF5 /* OnTapItemAnimationViewController.swift */; };
0ACF96D624A0094D0090EAC4 /* ItemInsertAndRemoveAnimationsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ACF96D524A0094D0090EAC4 /* ItemInsertAndRemoveAnimationsViewController.swift */; };
0AD6767A25423BE500A49315 /* MultiSelectViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AD6767925423BE500A49315 /* MultiSelectViewController.swift */; };
0ADC3B3524907C80008DF2C0 /* XcodePreviewDemo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ADC3B3424907C80008DF2C0 /* XcodePreviewDemo.swift */; };
Expand Down Expand Up @@ -74,6 +75,7 @@
0AA4D9B7248064A300CF95A5 /* ReorderingViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReorderingViewController.swift; sourceTree = "<group>"; };
0AA4D9B8248064A300CF95A5 /* CollectionViewBasicDemoViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CollectionViewBasicDemoViewController.swift; sourceTree = "<group>"; };
0AC2A1952489F93E00779459 /* PagedViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PagedViewController.swift; sourceTree = "<group>"; };
0AC839A425EEAD110055CEF5 /* OnTapItemAnimationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnTapItemAnimationViewController.swift; sourceTree = "<group>"; };
0ACF96D524A0094D0090EAC4 /* ItemInsertAndRemoveAnimationsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemInsertAndRemoveAnimationsViewController.swift; sourceTree = "<group>"; };
0AD6767925423BE500A49315 /* MultiSelectViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultiSelectViewController.swift; sourceTree = "<group>"; };
0ADC3B3424907C80008DF2C0 /* XcodePreviewDemo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XcodePreviewDemo.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -153,6 +155,7 @@
2B8804642490844A003BB351 /* SpacingCustomizationViewController.swift */,
0AA4D9B5248064A300CF95A5 /* SwipeActionsViewController.swift */,
0AA4D9AE248064A300CF95A5 /* WidthCustomizationViewController.swift */,
0AC839A425EEAD110055CEF5 /* OnTapItemAnimationViewController.swift */,
);
path = "Demo Screens";
sourceTree = "<group>";
Expand Down Expand Up @@ -443,6 +446,7 @@
0A66420B254A317A007F6B2F /* AutoLayoutDemoViewController.swift in Sources */,
0AEB96E222FBCC1D00341DFF /* AppDelegate.swift in Sources */,
0A793B5824E4B53500850139 /* ManualSelectionManagementViewController.swift in Sources */,
0AC839A525EEAD110055CEF5 /* OnTapItemAnimationViewController.swift in Sources */,
0AA4D9C9248064A300CF95A5 /* CollectionViewBasicDemoViewController.swift in Sources */,
0AA4D9BC248064A300CF95A5 /* KeyboardTestingViewController.swift in Sources */,
0AA4D9C1248064A300CF95A5 /* CoordinatorViewController.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,17 +116,15 @@ fileprivate struct PodcastElement : BlueprintItemContent, Equatable

let actions: CoordinatorActions
let info: CoordinatorInfo

var view : View?


init(actions: CoordinatorActions, info: CoordinatorInfo)
{
self.actions = actions
self.info = info
}

func wasSelected() {
self.actions.update(animated: true) {
self.actions.update(animation: .default) {
$0.content.showBottomBar = true
}

Expand Down Expand Up @@ -157,7 +155,7 @@ fileprivate struct PodcastElement : BlueprintItemContent, Equatable
}

func wasDeselected() {
self.actions.update(animated: true) {
self.actions.update(animation: .default) {
$0.content.showBottomBar = false
}
}
Expand Down
112 changes: 112 additions & 0 deletions Demo/Sources/Demos/Demo Screens/OnTapItemAnimationViewController.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
//
// OnTapItemAnimationViewController.swift
// Demo
//
// Created by Kyle Van Essen on 3/2/21.
// Copyright © 2021 Kyle Van Essen. All rights reserved.
//

import ListableUI
import BlueprintUI
import BlueprintUICommonControls
import BlueprintUILists

final class OnTapItemAnimationViewController : ListViewController {

override func configure(list: inout ListProperties) {

list.layout = .demoLayout

list("items") { section in

section += items.map { item in
Item(item) { item in
item.selectionStyle = .tappable
}
}

}
}
}

fileprivate struct ItemRow : BlueprintItemContent, Equatable {

var name : String
var price : String
var onTapText : String = "Added"

var isShowingPrice : Bool = true

var identifier: Identifier<ItemRow> {
.init(name)
}

func element(with info: ApplyItemContentInfo) -> Element {

Row { row in
row.verticalAlignment = .center

row.addFlexible(child: Label(text: self.name) { label in
label.font = .systemFont(ofSize: 18.0, weight: .semibold)
})

row.addFlexible(child: Overlay { overlay in
overlay.add(
Label(text: self.price) { label in
label.font = .systemFont(ofSize: 16.0, weight: .semibold)
}
.opacity(isShowingPrice ? 1 : 0)
)

overlay.add(
Label(text: self.onTapText) { label in
label.font = .systemFont(ofSize: 16.0, weight: .semibold)
}
.opacity(isShowingPrice ? 0 : 1)
)
})
}
.inset(uniform: 10.0)
.box(background: .white(0.95), corners: .rounded(radius: 10))
}

func makeCoordinator(
actions: CoordinatorActions,
info: CoordinatorInfo
) -> OnTapCoordinator
{
OnTapCoordinator(actions: actions, info: info)
}
}

fileprivate final class OnTapCoordinator : ItemContentCoordinator {
var actions: ItemRow.CoordinatorActions
var info: ItemRow.CoordinatorInfo

init(actions: ItemRow.CoordinatorActions, info: ItemRow.CoordinatorInfo) {
self.actions = actions
self.info = info
}

typealias ItemContentType = ItemRow

func wasSelected() {
self.actions.update { item in
item.content.isShowingPrice = false
}

self.actions.update(after: 1.0) { item in
item.content.isShowingPrice = true
}
}
}


fileprivate let items : [ItemRow] = [
.init(name: "Coffee", price: "$4.00"),
.init(name: "Cold Brew", price: "$5.00"),
.init(name: "Espresso", price: "$6.00"),
.init(name: "Flat White", price: "$5.00"),
.init(name: "Iced Coffee", price: "$5.00"),
.init(name: "Latte", price: "$6.00")
]
29 changes: 22 additions & 7 deletions Demo/Sources/Demos/DemosRootViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -131,13 +131,6 @@ public final class DemosRootViewController : ListViewController
self.push(LocalizedCollationViewController())
})

section += Item(
DemoItem(text: "Item Content Coordinator"),
selectionStyle: .selectable(),
onSelect : { _ in
self.push(CoordinatorViewController())
})

section += Item(
DemoItem(text: "Item Insert & Remove Animations"),
selectionStyle: .selectable(),
Expand Down Expand Up @@ -168,6 +161,28 @@ public final class DemosRootViewController : ListViewController
})
}

list("coordinator") { section in

section.header = HeaderFooter(
DemoHeader(title: "Item Coordinator")
)

section += Item(
DemoItem(text: "Expand / Collapse Items"),
selectionStyle: .selectable(),
onSelect : { _ in
self.push(CoordinatorViewController())
})

section += Item(
DemoItem(text: "Animating On Tap"),
selectionStyle: .selectable(),
onSelect : { _ in
self.push(OnTapItemAnimationViewController())
})

}

list("layouts") { section in

section.header = HeaderFooter(
Expand Down
4 changes: 2 additions & 2 deletions ListableUI/Sources/AutoScrollAction.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ public enum AutoScrollAction {
_ destination : ScrollDestination? = nil,
onInsertOf insertedIdentifier: AnyIdentifier,
position: ScrollPosition,
animation: ScrollAnimation = .none,
animation: ViewAnimation = .none,
shouldPerform : @escaping (ListScrollPositionInfo) -> Bool = { _ in true },
didPerform : @escaping (ListScrollPositionInfo) -> () = { _ in }
) -> AutoScrollAction
Expand Down Expand Up @@ -115,7 +115,7 @@ extension AutoScrollAction
/// ----
/// The action will only be animated if it is animated, **and** the list update itself is
/// animated. Otherwise, no animation occurs.
public var animation : ScrollAnimation
public var animation : ViewAnimation

/// An additional check you may provide to approve or reject the scroll action.
public var shouldPerform : (ListScrollPositionInfo) -> Bool
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ protocol AnyPresentationItemState : AnyObject

protocol ItemContentCoordinatorDelegate : AnyObject
{
func coordinatorUpdated(for item : AnyItem, animated : Bool)
func coordinatorUpdated(for item : AnyItem)
}


Expand Down Expand Up @@ -155,16 +155,17 @@ extension PresentationState

weak var coordinatorDelegate = dependencies.coordinatorDelegate

self.coordination.actions.updateCallback = { [weak self, weak coordinatorDelegate] new, animated in
self.coordination.actions.updateCallback = { [weak self, weak coordinatorDelegate] new, animation in
guard let self = self, let delegate = coordinatorDelegate else {
return
}

self.setNew(item: new, reason: .updateFromItemCoordinator, updateCallbacks: UpdateCallbacks(.immediate))

delegate.coordinatorUpdated(for: self.anyModel, animated: animated)

self.applyToVisibleCell(with: dependencies.environmentProvider())
animation.perform {
self.applyToVisibleCell(with: dependencies.environmentProvider())
delegate.coordinatorUpdated(for: self.anyModel)
}
}

self.storage.didSetState = { [weak self] old, new in
Expand Down Expand Up @@ -398,15 +399,10 @@ extension PresentationState
}

if old.visibleCell != new.visibleCell {
if let cell = new.visibleCell {
let contentView = cell.contentContainer.contentView

coordinator.view = contentView
coordinator.willDisplay(with: contentView)
if new.visibleCell != nil {
coordinator.willDisplay()
} else {
if let view = old.visibleCell?.contentContainer.contentView {
coordinator.didEndDisplay(with: view)
}
coordinator.didEndDisplay()
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion ListableUI/Sources/Item/ItemContent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,7 @@ public extension ItemContent where Coordinator == DefaultItemContentCoordinator<
{
func makeCoordinator(actions : ItemContentCoordinatorActions<Self>, info : ItemContentCoordinatorInfo<Self>) -> Coordinator
{
DefaultItemContentCoordinator(actions: actions, info: info, view: nil)
DefaultItemContentCoordinator(actions: actions, info: info)
}
}

Expand Down
Loading