From 2ccfa4759a4ade61dd8dd8afabf401baacc17fce Mon Sep 17 00:00:00 2001 From: gwangbeom Date: Mon, 29 Oct 2018 15:23:57 +0900 Subject: [PATCH 1/9] Add CustomAnimationConvertible --- AloeStackView.xcodeproj/project.pbxproj | 20 ++++++ Sources/AloeStackView/AloeStackView.swift | 21 +++--- .../Animation/AnimationCoordinator.swift | 64 +++++++++++++++++++ Sources/AloeStackView/Animation/State.swift | 25 ++++++++ .../CustomAnimationConvertible.swift | 27 ++++++++ 5 files changed, 147 insertions(+), 10 deletions(-) create mode 100644 Sources/AloeStackView/Animation/AnimationCoordinator.swift create mode 100644 Sources/AloeStackView/Animation/State.swift create mode 100644 Sources/AloeStackView/Protocols/CustomAnimationConvertible.swift diff --git a/AloeStackView.xcodeproj/project.pbxproj b/AloeStackView.xcodeproj/project.pbxproj index 737ef3f..11528fb 100644 --- a/AloeStackView.xcodeproj/project.pbxproj +++ b/AloeStackView.xcodeproj/project.pbxproj @@ -7,6 +7,9 @@ objects = { /* Begin PBXBuildFile section */ + 4A67EB2121868D2C00D134F7 /* CustomAnimationConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A67EB2021868D2C00D134F7 /* CustomAnimationConvertible.swift */; }; + 4A67EB262186A94F00D134F7 /* State.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A67EB252186A94F00D134F7 /* State.swift */; }; + 4A67EB282186AB0400D134F7 /* AnimationCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A67EB272186AB0400D134F7 /* AnimationCoordinator.swift */; }; A74F6EDB216D5CB50054AA18 /* AloeStackView.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A74F6ED1216D5CB50054AA18 /* AloeStackView.framework */; }; A74F6EF6216D5EFF0054AA18 /* AloeStackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A74F6EED216D5EFE0054AA18 /* AloeStackView.swift */; }; A74F6EF7216D5EFF0054AA18 /* AloeStackViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A74F6EEE216D5EFE0054AA18 /* AloeStackViewController.swift */; }; @@ -29,6 +32,9 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 4A67EB2021868D2C00D134F7 /* CustomAnimationConvertible.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomAnimationConvertible.swift; sourceTree = ""; }; + 4A67EB252186A94F00D134F7 /* State.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = State.swift; sourceTree = ""; }; + 4A67EB272186AB0400D134F7 /* AnimationCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnimationCoordinator.swift; sourceTree = ""; }; A74F6ED1216D5CB50054AA18 /* AloeStackView.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = AloeStackView.framework; sourceTree = BUILT_PRODUCTS_DIR; }; A74F6EDA216D5CB50054AA18 /* AloeStackViewTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = AloeStackViewTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; A74F6EED216D5EFE0054AA18 /* AloeStackView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AloeStackView.swift; sourceTree = ""; }; @@ -64,6 +70,15 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 4A67EB222186A92100D134F7 /* Animation */ = { + isa = PBXGroup; + children = ( + 4A67EB272186AB0400D134F7 /* AnimationCoordinator.swift */, + 4A67EB252186A94F00D134F7 /* State.swift */, + ); + path = Animation; + sourceTree = ""; + }; A74F6EC7216D5CB40054AA18 = { isa = PBXGroup; children = ( @@ -101,6 +116,7 @@ A74F6EEE216D5EFE0054AA18 /* AloeStackViewController.swift */, A74F6EEF216D5EFE0054AA18 /* Views */, A74F6EF2216D5EFE0054AA18 /* Protocols */, + 4A67EB222186A92100D134F7 /* Animation */, ); path = AloeStackView; sourceTree = ""; @@ -120,6 +136,7 @@ A74F6EF3216D5EFE0054AA18 /* Tappable.swift */, A74F6EF4216D5EFE0054AA18 /* SeparatorHiding.swift */, A74F6EF5216D5EFE0054AA18 /* Highlightable.swift */, + 4A67EB2021868D2C00D134F7 /* CustomAnimationConvertible.swift */, ); path = Protocols; sourceTree = ""; @@ -249,7 +266,10 @@ buildActionMask = 2147483647; files = ( A74F6EF8216D5EFF0054AA18 /* SeparatorView.swift in Sources */, + 4A67EB2121868D2C00D134F7 /* CustomAnimationConvertible.swift in Sources */, + 4A67EB282186AB0400D134F7 /* AnimationCoordinator.swift in Sources */, A74F6EF7216D5EFF0054AA18 /* AloeStackViewController.swift in Sources */, + 4A67EB262186A94F00D134F7 /* State.swift in Sources */, A74F6EF6216D5EFF0054AA18 /* AloeStackView.swift in Sources */, A74F6EF9216D5EFF0054AA18 /* StackViewCell.swift in Sources */, A74F6EFC216D5EFF0054AA18 /* Highlightable.swift in Sources */, diff --git a/Sources/AloeStackView/AloeStackView.swift b/Sources/AloeStackView/AloeStackView.swift index 07aa92f..f23f1a8 100644 --- a/Sources/AloeStackView/AloeStackView.swift +++ b/Sources/AloeStackView/AloeStackView.swift @@ -237,10 +237,12 @@ open class AloeStackView: UIScrollView { guard let cell = row.superview as? StackViewCell, cell.isHidden != isHidden else { return } if animated { - UIView.animate(withDuration: 0.3) { + let state: State = isHidden ? .delete : .insert + let coordinator = AnimationCoordinator(target: cell, state: state, animations: { cell.isHidden = isHidden cell.layoutIfNeeded() - } + }) + coordinator.startAnimation() } else { cell.isHidden = isHidden } @@ -551,9 +553,10 @@ open class AloeStackView: UIScrollView { if animated { cell.alpha = 0 layoutIfNeeded() - UIView.animate(withDuration: 0.3) { + let coordinator = AnimationCoordinator(target: cell, state: .insert, animations: { cell.alpha = 1 - } + }) + coordinator.startAnimation() } } @@ -572,12 +575,10 @@ open class AloeStackView: UIScrollView { } if animated { - UIView.animate( - withDuration: 0.3, - animations: { - cell.isHidden = true - }, - completion: completion) + let coordinator = AnimationCoordinator(target: cell, state: .delete, animations: { + cell.isHidden = true + }, completion: completion) + coordinator.startAnimation() } else { completion(true) } diff --git a/Sources/AloeStackView/Animation/AnimationCoordinator.swift b/Sources/AloeStackView/Animation/AnimationCoordinator.swift new file mode 100644 index 0000000..b4d4288 --- /dev/null +++ b/Sources/AloeStackView/Animation/AnimationCoordinator.swift @@ -0,0 +1,64 @@ +// Created by gwangbeom on 10/29/18. +// Copyright 2018 Airbnb, Inc. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import UIKit + +/** + * An object that animates when the Row is inserted and deleted from the AloeStackView. + */ +public class AnimationCoordinator { + + // MARK: Internal + + internal init(target: StackViewCell, state: State, animations: @escaping () -> Void, completion: ((Bool) -> Void)? = nil) { + self.target = target + self.state = state + self.animations = animations + self.completion = completion + } + + internal func startAnimation() { + (target.contentView as? CustomAnimationConvertible)?.willAnimation(with: self) + UIView.animate(withDuration: AnimationCoordinator.defaultAnimationDuration, animations: { + self.animations() + self.alongsideAnimation?() + }) { success in + self.completion?(success) + self.alongsideCompletion?(success) + } + } + + // MARK: Public + + public let state: State + + /// AloeStackView gives you the opportunity to change state values ​​by taking over the animation behavior when Row is added and removed. + public func animate(alongsideAnimation animation: (() -> Void)?, completion: ((Bool) -> Void)? = nil) { + alongsideAnimation = animation + alongsideCompletion = completion + } + + // MARK: Private + + private let target: StackViewCell + private let animations: () -> Void + private let completion: ((Bool) -> Void)? + + private var alongsideAnimation: (() -> Void)? + private var alongsideCompletion: ((Bool) -> Void)? + + private static let defaultAnimationDuration: TimeInterval = 0.3 + +} diff --git a/Sources/AloeStackView/Animation/State.swift b/Sources/AloeStackView/Animation/State.swift new file mode 100644 index 0000000..6e05da4 --- /dev/null +++ b/Sources/AloeStackView/Animation/State.swift @@ -0,0 +1,25 @@ +// Created by gwangbeom on 10/29/18. +// Copyright 2018 Airbnb, Inc. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * Indicates that the row is added and removed from the AloeStackView. + */ +public enum State { + + case insert + + case delete + +} diff --git a/Sources/AloeStackView/Protocols/CustomAnimationConvertible.swift b/Sources/AloeStackView/Protocols/CustomAnimationConvertible.swift new file mode 100644 index 0000000..8a222bd --- /dev/null +++ b/Sources/AloeStackView/Protocols/CustomAnimationConvertible.swift @@ -0,0 +1,27 @@ +// Created by gwangbeom on 10/29/18. +// Copyright 2018 Airbnb, Inc. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * The rows in `AloeStackView` help Custom Animation to work. + * + * Rows that are added to an `AloeStackView` can conform to this protocol to have their + * You can freely change the state animation of each contentView when a row is inserted or deleted. + */ +public protocol CustomAnimationConvertible { + + /// Invoked when animated is true when inserting or deleting. + func willAnimation(with coordinator: AnimationCoordinator) + +} From ac252a65b677e399270197a3a8028563d1fe3b87 Mon Sep 17 00:00:00 2001 From: gwangbeom Date: Mon, 29 Oct 2018 15:24:17 +0900 Subject: [PATCH 2/9] Add example --- .../project.pbxproj | 9 +++ .../ViewControllers/MainViewController.swift | 29 ++++++++ .../Views/AnimatableDescRowView.swift | 71 +++++++++++++++++++ .../Views/AnimatableRowLabel.swift | 32 +++++++++ 4 files changed, 141 insertions(+) create mode 100644 Example/AloeStackViewExample/Views/AnimatableDescRowView.swift create mode 100644 Example/AloeStackViewExample/Views/AnimatableRowLabel.swift diff --git a/Example/AloeStackViewExample.xcodeproj/project.pbxproj b/Example/AloeStackViewExample.xcodeproj/project.pbxproj index ce2d73d..b2426e3 100644 --- a/Example/AloeStackViewExample.xcodeproj/project.pbxproj +++ b/Example/AloeStackViewExample.xcodeproj/project.pbxproj @@ -9,6 +9,9 @@ /* Begin PBXBuildFile section */ 4D42E5BA22E499FE00D51F93 /* AloeStackView.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A7497A9021746D9700BB692A /* AloeStackView.framework */; }; 4D42E5BB22E499FE00D51F93 /* AloeStackView.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = A7497A9021746D9700BB692A /* AloeStackView.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 4A67EB2E2186D8C100D134F7 /* AnimatableDescRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A67EB2D2186D8C100D134F7 /* AnimatableDescRowView.swift */; }; + 4A67EB302186D91F00D134F7 /* AnimatableRowLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A67EB2F2186D91F00D134F7 /* AnimatableRowLabel.swift */; }; + A7497A9121746D9700BB692A /* AloeStackView.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A7497A9021746D9700BB692A /* AloeStackView.framework */; }; A7497A932174725700BB692A /* SwitchRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7497A922174725700BB692A /* SwitchRowView.swift */; }; A7497A9721747DD600BB692A /* PhotoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7497A9621747DD600BB692A /* PhotoViewController.swift */; }; A7497A9921755AD100BB692A /* ExpandingRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7497A9821755AD100BB692A /* ExpandingRowView.swift */; }; @@ -33,6 +36,8 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 4A67EB2D2186D8C100D134F7 /* AnimatableDescRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnimatableDescRowView.swift; sourceTree = ""; }; + 4A67EB2F2186D91F00D134F7 /* AnimatableRowLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnimatableRowLabel.swift; sourceTree = ""; }; A7420FAB21715DF500F2A343 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; A7497A9021746D9700BB692A /* AloeStackView.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = AloeStackView.framework; sourceTree = BUILT_PRODUCTS_DIR; }; A7497A922174725700BB692A /* SwitchRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwitchRowView.swift; sourceTree = ""; }; @@ -96,6 +101,8 @@ children = ( A7497A922174725700BB692A /* SwitchRowView.swift */, A7497A9821755AD100BB692A /* ExpandingRowView.swift */, + 4A67EB2D2186D8C100D134F7 /* AnimatableDescRowView.swift */, + 4A67EB2F2186D91F00D134F7 /* AnimatableRowLabel.swift */, ); path = Views; sourceTree = ""; @@ -216,6 +223,8 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 4A67EB302186D91F00D134F7 /* AnimatableRowLabel.swift in Sources */, + 4A67EB2E2186D8C100D134F7 /* AnimatableDescRowView.swift in Sources */, A74F70BB217155FC0054AA18 /* MainViewController.swift in Sources */, A7497A9721747DD600BB692A /* PhotoViewController.swift in Sources */, A74F70B9217155FC0054AA18 /* AppDelegate.swift in Sources */, diff --git a/Example/AloeStackViewExample/ViewControllers/MainViewController.swift b/Example/AloeStackViewExample/ViewControllers/MainViewController.swift index 58b14bd..a44b0fb 100644 --- a/Example/AloeStackViewExample/ViewControllers/MainViewController.swift +++ b/Example/AloeStackViewExample/ViewControllers/MainViewController.swift @@ -41,6 +41,8 @@ public class MainViewController: AloeStackViewController { setUpDescriptionRow() setUpSwitchRow() setUpHiddenRows() + setUpAnimatableDescRow() + setUpHiddenAnimatableRow() setUpExpandingRowView() setUpHorizontalRow() setUpPhotoRow() @@ -91,6 +93,33 @@ public class MainViewController: AloeStackViewController { stackView.setSeparatorInset(forRows: Array(hiddenRows.dropLast()), inset: separatorInset) } + private func setUpAnimatableDescRow() { + let animatableRow = AnimatableDescRowView() + stackView.addRow(animatableRow) + stackView.setTapHandler(forRow: animatableRow) { [weak self] _ in + guard let `self` = self else { return } + animatableRow.isSelected = !animatableRow.isSelected + self.stackView.setRowHidden(self.animatableHiddenLabel, isHidden: !animatableRow.isSelected, animated: true) + } + } + + private let animatableHiddenLabel = AnimatableRowLabel() + + private func setUpHiddenAnimatableRow() { + animatableHiddenLabel.text = "Customizing Row Animation" + + stackView.addRow(animatableHiddenLabel) + stackView.hideRow(animatableHiddenLabel) + + let rowInset = UIEdgeInsets( + top: stackView.rowInset.top, + left: stackView.rowInset.left * 2, + bottom: stackView.rowInset.bottom, + right: stackView.rowInset.right) + + stackView.setInset(forRow: animatableHiddenLabel, inset: rowInset) + } + private func setUpExpandingRowView() { let expandingRow = ExpandingRowView() stackView.addRow(expandingRow) diff --git a/Example/AloeStackViewExample/Views/AnimatableDescRowView.swift b/Example/AloeStackViewExample/Views/AnimatableDescRowView.swift new file mode 100644 index 0000000..4fc64e7 --- /dev/null +++ b/Example/AloeStackViewExample/Views/AnimatableDescRowView.swift @@ -0,0 +1,71 @@ +// Created by gwangbeom on 10/29/18. +// Copyright 2018 Airbnb, Inc. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import UIKit + +public class AnimatableDescRowView: UIView { + + // MARK: Lifecycle + + public init() { + super.init(frame: .zero) + setUpViews() + setUpConstraints() + } + + public required init(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: Public + + public var isSelected: Bool = false + + // MARK: Private + + private let titleLabel = UILabel() + private let captionLabel = UILabel() + + private func setUpViews() { + setUpTitleLabel() + setUpCaptionLabel() + } + + private func setUpTitleLabel() { + titleLabel.text = "Customizing Row Animation" + titleLabel.translatesAutoresizingMaskIntoConstraints = false + titleLabel.font = UIFont.preferredFont(forTextStyle: .body) + addSubview(titleLabel) + } + + private func setUpCaptionLabel() { + captionLabel.translatesAutoresizingMaskIntoConstraints = false + captionLabel.font = UIFont.preferredFont(forTextStyle: .caption2) + captionLabel.textColor = .blue + captionLabel.text = "(Try tapping on the Row!)" + addSubview(captionLabel) + } + + private func setUpConstraints() { + NSLayoutConstraint.activate([ + titleLabel.topAnchor.constraint(equalTo: topAnchor), + titleLabel.leadingAnchor.constraint(equalTo: leadingAnchor), + captionLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 8), + captionLabel.leadingAnchor.constraint(equalTo: leadingAnchor), + captionLabel.bottomAnchor.constraint(equalTo: bottomAnchor) + ]) + } + +} diff --git a/Example/AloeStackViewExample/Views/AnimatableRowLabel.swift b/Example/AloeStackViewExample/Views/AnimatableRowLabel.swift new file mode 100644 index 0000000..c4c7afc --- /dev/null +++ b/Example/AloeStackViewExample/Views/AnimatableRowLabel.swift @@ -0,0 +1,32 @@ +// Created by gwangbeom on 10/29/18. +// Copyright 2018 Airbnb, Inc. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import UIKit +import AloeStackView + +public class AnimatableRowLabel: UILabel, CustomAnimationConvertible { + + public func willAnimation(with coordinator: AnimationCoordinator) { + let isInsertState = coordinator.state == .insert + transform = isInsertState ? CGAffineTransform(translationX: -100, y: 0) : .identity + alpha = isInsertState ? 0 : 1 + + coordinator.animate(alongsideAnimation: { + self.transform = isInsertState ? .identity : CGAffineTransform(translationX: -100, y: 0) + self.alpha = isInsertState ? 1 : 0 + }) + } + +} From 99633bb8dfcc05633a3306dcc18762b29e4b1314 Mon Sep 17 00:00:00 2001 From: gwangbeom Date: Mon, 12 Nov 2018 13:06:00 +0900 Subject: [PATCH 3/9] Sample source code review reflected --- .../project.pbxproj | 14 +++--- .../ViewControllers/MainViewController.swift | 46 +++++------------- ...Label.swift => CustomAnimatingLabel.swift} | 4 +- .../Views/ExpandingRowView.swift | 29 +---------- ...escRowView.swift => TitleCaptionRow.swift} | 48 +++++++++---------- 5 files changed, 47 insertions(+), 94 deletions(-) rename Example/AloeStackViewExample/Views/{AnimatableRowLabel.swift => CustomAnimatingLabel.swift} (87%) rename Example/AloeStackViewExample/Views/{AnimatableDescRowView.swift => TitleCaptionRow.swift} (64%) diff --git a/Example/AloeStackViewExample.xcodeproj/project.pbxproj b/Example/AloeStackViewExample.xcodeproj/project.pbxproj index b2426e3..286c2cf 100644 --- a/Example/AloeStackViewExample.xcodeproj/project.pbxproj +++ b/Example/AloeStackViewExample.xcodeproj/project.pbxproj @@ -11,6 +11,8 @@ 4D42E5BB22E499FE00D51F93 /* AloeStackView.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = A7497A9021746D9700BB692A /* AloeStackView.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 4A67EB2E2186D8C100D134F7 /* AnimatableDescRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A67EB2D2186D8C100D134F7 /* AnimatableDescRowView.swift */; }; 4A67EB302186D91F00D134F7 /* AnimatableRowLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A67EB2F2186D91F00D134F7 /* AnimatableRowLabel.swift */; }; + 4A67EB2E2186D8C100D134F7 /* TitleCaptionRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A67EB2D2186D8C100D134F7 /* TitleCaptionRow.swift */; }; + 4A67EB302186D91F00D134F7 /* CustomAnimatingLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A67EB2F2186D91F00D134F7 /* CustomAnimatingLabel.swift */; }; A7497A9121746D9700BB692A /* AloeStackView.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A7497A9021746D9700BB692A /* AloeStackView.framework */; }; A7497A932174725700BB692A /* SwitchRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7497A922174725700BB692A /* SwitchRowView.swift */; }; A7497A9721747DD600BB692A /* PhotoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7497A9621747DD600BB692A /* PhotoViewController.swift */; }; @@ -36,8 +38,8 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ - 4A67EB2D2186D8C100D134F7 /* AnimatableDescRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnimatableDescRowView.swift; sourceTree = ""; }; - 4A67EB2F2186D91F00D134F7 /* AnimatableRowLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnimatableRowLabel.swift; sourceTree = ""; }; + 4A67EB2D2186D8C100D134F7 /* TitleCaptionRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TitleCaptionRow.swift; sourceTree = ""; }; + 4A67EB2F2186D91F00D134F7 /* CustomAnimatingLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomAnimatingLabel.swift; sourceTree = ""; }; A7420FAB21715DF500F2A343 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; A7497A9021746D9700BB692A /* AloeStackView.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = AloeStackView.framework; sourceTree = BUILT_PRODUCTS_DIR; }; A7497A922174725700BB692A /* SwitchRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwitchRowView.swift; sourceTree = ""; }; @@ -99,10 +101,10 @@ A7420FA921715CC400F2A343 /* Views */ = { isa = PBXGroup; children = ( + 4A67EB2D2186D8C100D134F7 /* TitleCaptionRow.swift */, A7497A922174725700BB692A /* SwitchRowView.swift */, A7497A9821755AD100BB692A /* ExpandingRowView.swift */, - 4A67EB2D2186D8C100D134F7 /* AnimatableDescRowView.swift */, - 4A67EB2F2186D91F00D134F7 /* AnimatableRowLabel.swift */, + 4A67EB2F2186D91F00D134F7 /* CustomAnimatingLabel.swift */, ); path = Views; sourceTree = ""; @@ -223,8 +225,8 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 4A67EB302186D91F00D134F7 /* AnimatableRowLabel.swift in Sources */, - 4A67EB2E2186D8C100D134F7 /* AnimatableDescRowView.swift in Sources */, + 4A67EB302186D91F00D134F7 /* CustomAnimatingLabel.swift in Sources */, + 4A67EB2E2186D8C100D134F7 /* TitleCaptionRow.swift in Sources */, A74F70BB217155FC0054AA18 /* MainViewController.swift in Sources */, A7497A9721747DD600BB692A /* PhotoViewController.swift in Sources */, A74F70B9217155FC0054AA18 /* AppDelegate.swift in Sources */, diff --git a/Example/AloeStackViewExample/ViewControllers/MainViewController.swift b/Example/AloeStackViewExample/ViewControllers/MainViewController.swift index a44b0fb..791c40e 100644 --- a/Example/AloeStackViewExample/ViewControllers/MainViewController.swift +++ b/Example/AloeStackViewExample/ViewControllers/MainViewController.swift @@ -41,7 +41,7 @@ public class MainViewController: AloeStackViewController { setUpDescriptionRow() setUpSwitchRow() setUpHiddenRows() - setUpAnimatableDescRow() + setUpCustomAnimatingDescRow() setUpHiddenAnimatableRow() setUpExpandingRowView() setUpHorizontalRow() @@ -49,11 +49,8 @@ public class MainViewController: AloeStackViewController { } private func setUpDescriptionRow() { - let label = UILabel() - label.font = UIFont.preferredFont(forTextStyle: .body) - label.numberOfLines = 0 - label.text = "This simple app shows some ways you can use AloeStackView to lay out a screen in your app." - stackView.addRow(label) + let descriptionRow = TitleCaptionRow(title: "This simple app shows some ways you can use AloeStackView to lay out a screen in your app.") + stackView.addRow(descriptionRow) } private func setUpSwitchRow() { @@ -93,17 +90,17 @@ public class MainViewController: AloeStackViewController { stackView.setSeparatorInset(forRows: Array(hiddenRows.dropLast()), inset: separatorInset) } - private func setUpAnimatableDescRow() { - let animatableRow = AnimatableDescRowView() + private func setUpCustomAnimatingDescRow() { + let animatableRow = TitleCaptionRow(title: "Customizing Row Animation", captionText: "(Try tapping on the Row!)") stackView.addRow(animatableRow) stackView.setTapHandler(forRow: animatableRow) { [weak self] _ in guard let `self` = self else { return } - animatableRow.isSelected = !animatableRow.isSelected - self.stackView.setRowHidden(self.animatableHiddenLabel, isHidden: !animatableRow.isSelected, animated: true) + let isHidden = self.stackView.isRowHidden(self.animatableHiddenLabel) + self.stackView.setRowHidden(self.animatableHiddenLabel, isHidden: !isHidden, animated: true) } } - private let animatableHiddenLabel = AnimatableRowLabel() + private let animatableHiddenLabel = CustomAnimatingLabel() private func setUpHiddenAnimatableRow() { animatableHiddenLabel.text = "Customizing Row Animation" @@ -197,30 +194,9 @@ public class MainViewController: AloeStackViewController { } private func setUpPhotoRow() { - let titleLabel = UILabel() - titleLabel.font = UIFont.preferredFont(forTextStyle: .body) - titleLabel.numberOfLines = 0 - titleLabel.text = "Handle user interaction" - stackView.addRow(titleLabel) - stackView.hideSeparator(forRow: titleLabel) - stackView.setInset(forRow: titleLabel, inset: UIEdgeInsets( - top: stackView.rowInset.top, - left: stackView.rowInset.left, - bottom: 4, - right: stackView.rowInset.right)) - - let captionLabel = UILabel() - captionLabel.font = UIFont.preferredFont(forTextStyle: .caption2) - captionLabel.textColor = .blue - captionLabel.numberOfLines = 0 - captionLabel.text = "(Try tapping on the photo!)" - stackView.addRow(captionLabel) - stackView.hideSeparator(forRow: captionLabel) - stackView.setInset(forRow: captionLabel, inset: UIEdgeInsets( - top: 0, - left: stackView.rowInset.left, - bottom: stackView.rowInset.bottom, - right: stackView.rowInset.right)) + let row = TitleCaptionRow(title: "Handle user interaction", captionText: "(Try tapping on the photo!)") + stackView.addRow(row) + stackView.hideSeparator(forRow: row) guard let image = UIImage(named: "lobster-dog") else { return } let aspectRatio = image.size.height / image.size.width diff --git a/Example/AloeStackViewExample/Views/AnimatableRowLabel.swift b/Example/AloeStackViewExample/Views/CustomAnimatingLabel.swift similarity index 87% rename from Example/AloeStackViewExample/Views/AnimatableRowLabel.swift rename to Example/AloeStackViewExample/Views/CustomAnimatingLabel.swift index c4c7afc..187c121 100644 --- a/Example/AloeStackViewExample/Views/AnimatableRowLabel.swift +++ b/Example/AloeStackViewExample/Views/CustomAnimatingLabel.swift @@ -16,9 +16,9 @@ import UIKit import AloeStackView -public class AnimatableRowLabel: UILabel, CustomAnimationConvertible { +public class CustomAnimatingLabel: UILabel, CustomAnimating { - public func willAnimation(with coordinator: AnimationCoordinator) { + public func willAnimate(with coordinator: AnimationCoordinator) { let isInsertState = coordinator.state == .insert transform = isInsertState ? CGAffineTransform(translationX: -100, y: 0) : .identity alpha = isInsertState ? 0 : 1 diff --git a/Example/AloeStackViewExample/Views/ExpandingRowView.swift b/Example/AloeStackViewExample/Views/ExpandingRowView.swift index 32c20b8..0c93e2a 100644 --- a/Example/AloeStackViewExample/Views/ExpandingRowView.swift +++ b/Example/AloeStackViewExample/Views/ExpandingRowView.swift @@ -16,13 +16,12 @@ import AloeStackView import UIKit -public class ExpandingRowView: UIStackView, Tappable, Highlightable { +public class ExpandingRowView: TitleCaptionRow, Tappable, Highlightable { // MARK: Lifecycle public init() { - super.init(frame: .zero) - translatesAutoresizingMaskIntoConstraints = false + super.init(title: "Dynamically change row content", captionText: "(Tap on this row to add more content!)\n") setUpViews() } @@ -42,38 +41,14 @@ public class ExpandingRowView: UIStackView, Tappable, Highlightable { // MARK: Private - private let titleLabel = UILabel() - private let showMoreLabel = UILabel() private let textLabel = UILabel() private var nextLine = 1 private func setUpViews() { - setUpSelf() - setUpTitleLabel() - setUpShowMoreLabel() setUpTextLabel() } - private func setUpSelf() { - axis = .vertical - spacing = 4 - } - - private func setUpTitleLabel() { - titleLabel.text = "Dynamically change row content" - titleLabel.font = UIFont.preferredFont(forTextStyle: .body) - addArrangedSubview(titleLabel) - } - - private func setUpShowMoreLabel() { - showMoreLabel.numberOfLines = 0 - showMoreLabel.text = "(Tap on this row to add more content!)\n" - showMoreLabel.font = UIFont.preferredFont(forTextStyle: .caption2) - showMoreLabel.textColor = .blue - addArrangedSubview(showMoreLabel) - } - private func setUpTextLabel() { textLabel.numberOfLines = 0 textLabel.font = UIFont.preferredFont(forTextStyle: .caption2) diff --git a/Example/AloeStackViewExample/Views/AnimatableDescRowView.swift b/Example/AloeStackViewExample/Views/TitleCaptionRow.swift similarity index 64% rename from Example/AloeStackViewExample/Views/AnimatableDescRowView.swift rename to Example/AloeStackViewExample/Views/TitleCaptionRow.swift index 4fc64e7..e51b26a 100644 --- a/Example/AloeStackViewExample/Views/AnimatableDescRowView.swift +++ b/Example/AloeStackViewExample/Views/TitleCaptionRow.swift @@ -15,57 +15,57 @@ import UIKit -public class AnimatableDescRowView: UIView { +public class TitleCaptionRow: UIStackView { // MARK: Lifecycle - public init() { + public init(title: String, captionText: String? = nil) { + self.title = title + self.captionText = captionText super.init(frame: .zero) + setUpSelf() setUpViews() - setUpConstraints() } public required init(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } - // MARK: Public + // MARK: Private - public var isSelected: Bool = false + private let title: String + private let captionText: String? - // MARK: Private - private let titleLabel = UILabel() private let captionLabel = UILabel() - + + private func setUpSelf() { + translatesAutoresizingMaskIntoConstraints = false + axis = .vertical + spacing = 4 + } + private func setUpViews() { setUpTitleLabel() setUpCaptionLabel() } - + private func setUpTitleLabel() { - titleLabel.text = "Customizing Row Animation" + titleLabel.text = title + titleLabel.numberOfLines = 0 titleLabel.translatesAutoresizingMaskIntoConstraints = false titleLabel.font = UIFont.preferredFont(forTextStyle: .body) - addSubview(titleLabel) + addArrangedSubview(titleLabel) } - + private func setUpCaptionLabel() { + guard let captionText = captionText else { return } + captionLabel.text = captionText + captionLabel.numberOfLines = 0 captionLabel.translatesAutoresizingMaskIntoConstraints = false captionLabel.font = UIFont.preferredFont(forTextStyle: .caption2) captionLabel.textColor = .blue - captionLabel.text = "(Try tapping on the Row!)" - addSubview(captionLabel) - } - - private func setUpConstraints() { - NSLayoutConstraint.activate([ - titleLabel.topAnchor.constraint(equalTo: topAnchor), - titleLabel.leadingAnchor.constraint(equalTo: leadingAnchor), - captionLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 8), - captionLabel.leadingAnchor.constraint(equalTo: leadingAnchor), - captionLabel.bottomAnchor.constraint(equalTo: bottomAnchor) - ]) + addArrangedSubview(captionLabel) } } From 6f1960fb9ff630e7ed93603bd73505eb298b2f8b Mon Sep 17 00:00:00 2001 From: gwangbeom Date: Mon, 12 Nov 2018 13:06:14 +0900 Subject: [PATCH 4/9] Reflected reviews --- Sources/AloeStackView/Animation/AnimationCoordinator.swift | 2 +- .../AloeStackView/Protocols/CustomAnimationConvertible.swift | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/AloeStackView/Animation/AnimationCoordinator.swift b/Sources/AloeStackView/Animation/AnimationCoordinator.swift index b4d4288..7b6c107 100644 --- a/Sources/AloeStackView/Animation/AnimationCoordinator.swift +++ b/Sources/AloeStackView/Animation/AnimationCoordinator.swift @@ -30,7 +30,7 @@ public class AnimationCoordinator { } internal func startAnimation() { - (target.contentView as? CustomAnimationConvertible)?.willAnimation(with: self) + (target.contentView as? CustomAnimating)?.willAnimate(with: self) UIView.animate(withDuration: AnimationCoordinator.defaultAnimationDuration, animations: { self.animations() self.alongsideAnimation?() diff --git a/Sources/AloeStackView/Protocols/CustomAnimationConvertible.swift b/Sources/AloeStackView/Protocols/CustomAnimationConvertible.swift index 8a222bd..66dabf9 100644 --- a/Sources/AloeStackView/Protocols/CustomAnimationConvertible.swift +++ b/Sources/AloeStackView/Protocols/CustomAnimationConvertible.swift @@ -19,9 +19,9 @@ * Rows that are added to an `AloeStackView` can conform to this protocol to have their * You can freely change the state animation of each contentView when a row is inserted or deleted. */ -public protocol CustomAnimationConvertible { +public protocol CustomAnimating { /// Invoked when animated is true when inserting or deleting. - func willAnimation(with coordinator: AnimationCoordinator) + func willAnimate(with coordinator: AnimationCoordinator) } From b1fdbdd26f56997b3bec7c3172241e2a0d52ce29 Mon Sep 17 00:00:00 2001 From: gwangbeom Date: Mon, 12 Nov 2018 13:42:25 +0900 Subject: [PATCH 5/9] Modify the code style to suit --- Sources/AloeStackView/AloeStackView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/AloeStackView/AloeStackView.swift b/Sources/AloeStackView/AloeStackView.swift index f23f1a8..0c7fe83 100644 --- a/Sources/AloeStackView/AloeStackView.swift +++ b/Sources/AloeStackView/AloeStackView.swift @@ -238,7 +238,7 @@ open class AloeStackView: UIScrollView { if animated { let state: State = isHidden ? .delete : .insert - let coordinator = AnimationCoordinator(target: cell, state: state, animations: { + let coordinator = AnimationCoordinator(target: cell, state: state, animations: { cell.isHidden = isHidden cell.layoutIfNeeded() }) From 10ac52a43812fd9ec740871a49a5d511465f90a6 Mon Sep 17 00:00:00 2001 From: gwangbeom Date: Tue, 13 Nov 2018 13:47:43 +0900 Subject: [PATCH 6/9] Reflected reviews --- AloeStackView.xcodeproj/project.pbxproj | 8 +++--- .../project.pbxproj | 10 +++---- .../ViewControllers/MainViewController.swift | 28 +++++++++---------- .../Views/ExpandingRowView.swift | 4 +-- ...ionRow.swift => TitleCaptionRowView.swift} | 17 ++++------- ...onvertible.swift => CustomAnimating.swift} | 0 6 files changed, 30 insertions(+), 37 deletions(-) rename Example/AloeStackViewExample/Views/{TitleCaptionRow.swift => TitleCaptionRowView.swift} (84%) rename Sources/AloeStackView/Protocols/{CustomAnimationConvertible.swift => CustomAnimating.swift} (100%) diff --git a/AloeStackView.xcodeproj/project.pbxproj b/AloeStackView.xcodeproj/project.pbxproj index 11528fb..f7f2345 100644 --- a/AloeStackView.xcodeproj/project.pbxproj +++ b/AloeStackView.xcodeproj/project.pbxproj @@ -7,7 +7,7 @@ objects = { /* Begin PBXBuildFile section */ - 4A67EB2121868D2C00D134F7 /* CustomAnimationConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A67EB2021868D2C00D134F7 /* CustomAnimationConvertible.swift */; }; + 4A67EB2121868D2C00D134F7 /* CustomAnimating.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A67EB2021868D2C00D134F7 /* CustomAnimating.swift */; }; 4A67EB262186A94F00D134F7 /* State.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A67EB252186A94F00D134F7 /* State.swift */; }; 4A67EB282186AB0400D134F7 /* AnimationCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A67EB272186AB0400D134F7 /* AnimationCoordinator.swift */; }; A74F6EDB216D5CB50054AA18 /* AloeStackView.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A74F6ED1216D5CB50054AA18 /* AloeStackView.framework */; }; @@ -32,7 +32,7 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ - 4A67EB2021868D2C00D134F7 /* CustomAnimationConvertible.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomAnimationConvertible.swift; sourceTree = ""; }; + 4A67EB2021868D2C00D134F7 /* CustomAnimating.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomAnimating.swift; sourceTree = ""; }; 4A67EB252186A94F00D134F7 /* State.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = State.swift; sourceTree = ""; }; 4A67EB272186AB0400D134F7 /* AnimationCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnimationCoordinator.swift; sourceTree = ""; }; A74F6ED1216D5CB50054AA18 /* AloeStackView.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = AloeStackView.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -136,7 +136,7 @@ A74F6EF3216D5EFE0054AA18 /* Tappable.swift */, A74F6EF4216D5EFE0054AA18 /* SeparatorHiding.swift */, A74F6EF5216D5EFE0054AA18 /* Highlightable.swift */, - 4A67EB2021868D2C00D134F7 /* CustomAnimationConvertible.swift */, + 4A67EB2021868D2C00D134F7 /* CustomAnimating.swift */, ); path = Protocols; sourceTree = ""; @@ -266,7 +266,7 @@ buildActionMask = 2147483647; files = ( A74F6EF8216D5EFF0054AA18 /* SeparatorView.swift in Sources */, - 4A67EB2121868D2C00D134F7 /* CustomAnimationConvertible.swift in Sources */, + 4A67EB2121868D2C00D134F7 /* CustomAnimating.swift in Sources */, 4A67EB282186AB0400D134F7 /* AnimationCoordinator.swift in Sources */, A74F6EF7216D5EFF0054AA18 /* AloeStackViewController.swift in Sources */, 4A67EB262186A94F00D134F7 /* State.swift in Sources */, diff --git a/Example/AloeStackViewExample.xcodeproj/project.pbxproj b/Example/AloeStackViewExample.xcodeproj/project.pbxproj index 286c2cf..c4687ff 100644 --- a/Example/AloeStackViewExample.xcodeproj/project.pbxproj +++ b/Example/AloeStackViewExample.xcodeproj/project.pbxproj @@ -9,9 +9,7 @@ /* Begin PBXBuildFile section */ 4D42E5BA22E499FE00D51F93 /* AloeStackView.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A7497A9021746D9700BB692A /* AloeStackView.framework */; }; 4D42E5BB22E499FE00D51F93 /* AloeStackView.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = A7497A9021746D9700BB692A /* AloeStackView.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - 4A67EB2E2186D8C100D134F7 /* AnimatableDescRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A67EB2D2186D8C100D134F7 /* AnimatableDescRowView.swift */; }; - 4A67EB302186D91F00D134F7 /* AnimatableRowLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A67EB2F2186D91F00D134F7 /* AnimatableRowLabel.swift */; }; - 4A67EB2E2186D8C100D134F7 /* TitleCaptionRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A67EB2D2186D8C100D134F7 /* TitleCaptionRow.swift */; }; + 4A67EB2E2186D8C100D134F7 /* TitleCaptionRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A67EB2D2186D8C100D134F7 /* TitleCaptionRowView.swift */; }; 4A67EB302186D91F00D134F7 /* CustomAnimatingLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A67EB2F2186D91F00D134F7 /* CustomAnimatingLabel.swift */; }; A7497A9121746D9700BB692A /* AloeStackView.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A7497A9021746D9700BB692A /* AloeStackView.framework */; }; A7497A932174725700BB692A /* SwitchRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7497A922174725700BB692A /* SwitchRowView.swift */; }; @@ -38,7 +36,7 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ - 4A67EB2D2186D8C100D134F7 /* TitleCaptionRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TitleCaptionRow.swift; sourceTree = ""; }; + 4A67EB2D2186D8C100D134F7 /* TitleCaptionRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TitleCaptionRowView.swift; sourceTree = ""; }; 4A67EB2F2186D91F00D134F7 /* CustomAnimatingLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomAnimatingLabel.swift; sourceTree = ""; }; A7420FAB21715DF500F2A343 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; A7497A9021746D9700BB692A /* AloeStackView.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = AloeStackView.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -101,7 +99,7 @@ A7420FA921715CC400F2A343 /* Views */ = { isa = PBXGroup; children = ( - 4A67EB2D2186D8C100D134F7 /* TitleCaptionRow.swift */, + 4A67EB2D2186D8C100D134F7 /* TitleCaptionRowView.swift */, A7497A922174725700BB692A /* SwitchRowView.swift */, A7497A9821755AD100BB692A /* ExpandingRowView.swift */, 4A67EB2F2186D91F00D134F7 /* CustomAnimatingLabel.swift */, @@ -226,7 +224,7 @@ buildActionMask = 2147483647; files = ( 4A67EB302186D91F00D134F7 /* CustomAnimatingLabel.swift in Sources */, - 4A67EB2E2186D8C100D134F7 /* TitleCaptionRow.swift in Sources */, + 4A67EB2E2186D8C100D134F7 /* TitleCaptionRowView.swift in Sources */, A74F70BB217155FC0054AA18 /* MainViewController.swift in Sources */, A7497A9721747DD600BB692A /* PhotoViewController.swift in Sources */, A74F70B9217155FC0054AA18 /* AppDelegate.swift in Sources */, diff --git a/Example/AloeStackViewExample/ViewControllers/MainViewController.swift b/Example/AloeStackViewExample/ViewControllers/MainViewController.swift index 791c40e..6569e72 100644 --- a/Example/AloeStackViewExample/ViewControllers/MainViewController.swift +++ b/Example/AloeStackViewExample/ViewControllers/MainViewController.swift @@ -41,15 +41,15 @@ public class MainViewController: AloeStackViewController { setUpDescriptionRow() setUpSwitchRow() setUpHiddenRows() - setUpCustomAnimatingDescRow() - setUpHiddenAnimatableRow() + setUpCustomAnimatingDescriptionRow() + setUpCustomAnimatingRow() setUpExpandingRowView() setUpHorizontalRow() setUpPhotoRow() } private func setUpDescriptionRow() { - let descriptionRow = TitleCaptionRow(title: "This simple app shows some ways you can use AloeStackView to lay out a screen in your app.") + let descriptionRow = TitleCaptionRowView(titleText: "This simple app shows some ways you can use AloeStackView to lay out a screen in your app.") stackView.addRow(descriptionRow) } @@ -90,23 +90,23 @@ public class MainViewController: AloeStackViewController { stackView.setSeparatorInset(forRows: Array(hiddenRows.dropLast()), inset: separatorInset) } - private func setUpCustomAnimatingDescRow() { - let animatableRow = TitleCaptionRow(title: "Customizing Row Animation", captionText: "(Try tapping on the Row!)") + private func setUpCustomAnimatingDescriptionRow() { + let animatableRow = TitleCaptionRowView(titleText: "Customizing Row Animation", captionText: "(Try tapping on the Row!)") stackView.addRow(animatableRow) stackView.setTapHandler(forRow: animatableRow) { [weak self] _ in guard let `self` = self else { return } - let isHidden = self.stackView.isRowHidden(self.animatableHiddenLabel) - self.stackView.setRowHidden(self.animatableHiddenLabel, isHidden: !isHidden, animated: true) + let isHidden = self.stackView.isRowHidden(self.customAnimatingLabel) + self.stackView.setRowHidden(self.customAnimatingLabel, isHidden: !isHidden, animated: true) } } - private let animatableHiddenLabel = CustomAnimatingLabel() + private let customAnimatingLabel = CustomAnimatingLabel() - private func setUpHiddenAnimatableRow() { - animatableHiddenLabel.text = "Customizing Row Animation" + private func setUpCustomAnimatingRow() { + customAnimatingLabel.text = "Customizing Row Animation" - stackView.addRow(animatableHiddenLabel) - stackView.hideRow(animatableHiddenLabel) + stackView.addRow(customAnimatingLabel) + stackView.hideRow(customAnimatingLabel) let rowInset = UIEdgeInsets( top: stackView.rowInset.top, @@ -114,7 +114,7 @@ public class MainViewController: AloeStackViewController { bottom: stackView.rowInset.bottom, right: stackView.rowInset.right) - stackView.setInset(forRow: animatableHiddenLabel, inset: rowInset) + stackView.setInset(forRow: customAnimatingLabel, inset: rowInset) } private func setUpExpandingRowView() { @@ -194,7 +194,7 @@ public class MainViewController: AloeStackViewController { } private func setUpPhotoRow() { - let row = TitleCaptionRow(title: "Handle user interaction", captionText: "(Try tapping on the photo!)") + let row = TitleCaptionRowView(titleText: "Handle user interaction", captionText: "(Try tapping on the photo!)") stackView.addRow(row) stackView.hideSeparator(forRow: row) diff --git a/Example/AloeStackViewExample/Views/ExpandingRowView.swift b/Example/AloeStackViewExample/Views/ExpandingRowView.swift index 0c93e2a..76c11bd 100644 --- a/Example/AloeStackViewExample/Views/ExpandingRowView.swift +++ b/Example/AloeStackViewExample/Views/ExpandingRowView.swift @@ -16,12 +16,12 @@ import AloeStackView import UIKit -public class ExpandingRowView: TitleCaptionRow, Tappable, Highlightable { +public class ExpandingRowView: TitleCaptionRowView, Tappable, Highlightable { // MARK: Lifecycle public init() { - super.init(title: "Dynamically change row content", captionText: "(Tap on this row to add more content!)\n") + super.init(titleText: "Dynamically change row content", captionText: "(Tap on this row to add more content!)\n") setUpViews() } diff --git a/Example/AloeStackViewExample/Views/TitleCaptionRow.swift b/Example/AloeStackViewExample/Views/TitleCaptionRowView.swift similarity index 84% rename from Example/AloeStackViewExample/Views/TitleCaptionRow.swift rename to Example/AloeStackViewExample/Views/TitleCaptionRowView.swift index e51b26a..033f890 100644 --- a/Example/AloeStackViewExample/Views/TitleCaptionRow.swift +++ b/Example/AloeStackViewExample/Views/TitleCaptionRowView.swift @@ -15,15 +15,14 @@ import UIKit -public class TitleCaptionRow: UIStackView { +public class TitleCaptionRowView: UIStackView { // MARK: Lifecycle - public init(title: String, captionText: String? = nil) { - self.title = title - self.captionText = captionText + public init(titleText: String, captionText: String? = nil) { super.init(frame: .zero) - setUpSelf() + titleLabel.text = titleText + captionLabel.text = captionText setUpViews() } @@ -33,9 +32,6 @@ public class TitleCaptionRow: UIStackView { // MARK: Private - private let title: String - private let captionText: String? - private let titleLabel = UILabel() private let captionLabel = UILabel() @@ -46,12 +42,12 @@ public class TitleCaptionRow: UIStackView { } private func setUpViews() { + setUpSelf() setUpTitleLabel() setUpCaptionLabel() } private func setUpTitleLabel() { - titleLabel.text = title titleLabel.numberOfLines = 0 titleLabel.translatesAutoresizingMaskIntoConstraints = false titleLabel.font = UIFont.preferredFont(forTextStyle: .body) @@ -59,8 +55,7 @@ public class TitleCaptionRow: UIStackView { } private func setUpCaptionLabel() { - guard let captionText = captionText else { return } - captionLabel.text = captionText + guard captionLabel.text != nil else { return } captionLabel.numberOfLines = 0 captionLabel.translatesAutoresizingMaskIntoConstraints = false captionLabel.font = UIFont.preferredFont(forTextStyle: .caption2) diff --git a/Sources/AloeStackView/Protocols/CustomAnimationConvertible.swift b/Sources/AloeStackView/Protocols/CustomAnimating.swift similarity index 100% rename from Sources/AloeStackView/Protocols/CustomAnimationConvertible.swift rename to Sources/AloeStackView/Protocols/CustomAnimating.swift From b5bb1a2d5e99157c162f2d7401613bc13adda7cc Mon Sep 17 00:00:00 2001 From: gwangbeom Date: Sun, 17 Mar 2019 18:56:10 +0900 Subject: [PATCH 7/9] Reflected reviews --- .../Views/CustomAnimatingLabel.swift | 26 +++++---- Sources/AloeStackView/AloeStackView.swift | 4 +- .../Animation/AnimationCoordinator.swift | 56 ++++++++++++------- Sources/AloeStackView/Animation/State.swift | 6 +- .../Protocols/CustomAnimating.swift | 50 ++++++++++++++++- 5 files changed, 104 insertions(+), 38 deletions(-) diff --git a/Example/AloeStackViewExample/Views/CustomAnimatingLabel.swift b/Example/AloeStackViewExample/Views/CustomAnimatingLabel.swift index 187c121..caafc42 100644 --- a/Example/AloeStackViewExample/Views/CustomAnimatingLabel.swift +++ b/Example/AloeStackViewExample/Views/CustomAnimatingLabel.swift @@ -17,16 +17,20 @@ import UIKit import AloeStackView public class CustomAnimatingLabel: UILabel, CustomAnimating { - - public func willAnimate(with coordinator: AnimationCoordinator) { - let isInsertState = coordinator.state == .insert - transform = isInsertState ? CGAffineTransform(translationX: -100, y: 0) : .identity - alpha = isInsertState ? 0 : 1 - - coordinator.animate(alongsideAnimation: { - self.transform = isInsertState ? .identity : CGAffineTransform(translationX: -100, y: 0) - self.alpha = isInsertState ? 1 : 0 - }) - } + + public func animateInsert() { + transform = .identity + alpha = 1 + } + + public func insertAnimationWillBegin() { + transform = CGAffineTransform(translationX: -100, y: 0) + alpha = 0 + } + + public func animateRemove() { + transform = CGAffineTransform(translationX: -100, y: 0) + alpha = 0 + } } diff --git a/Sources/AloeStackView/AloeStackView.swift b/Sources/AloeStackView/AloeStackView.swift index 0c7fe83..2ed9280 100644 --- a/Sources/AloeStackView/AloeStackView.swift +++ b/Sources/AloeStackView/AloeStackView.swift @@ -237,7 +237,7 @@ open class AloeStackView: UIScrollView { guard let cell = row.superview as? StackViewCell, cell.isHidden != isHidden else { return } if animated { - let state: State = isHidden ? .delete : .insert + let state: State = isHidden ? .remove : .insert let coordinator = AnimationCoordinator(target: cell, state: state, animations: { cell.isHidden = isHidden cell.layoutIfNeeded() @@ -575,7 +575,7 @@ open class AloeStackView: UIScrollView { } if animated { - let coordinator = AnimationCoordinator(target: cell, state: .delete, animations: { + let coordinator = AnimationCoordinator(target: cell, state: .remove, animations: { cell.isHidden = true }, completion: completion) coordinator.startAnimation() diff --git a/Sources/AloeStackView/Animation/AnimationCoordinator.swift b/Sources/AloeStackView/Animation/AnimationCoordinator.swift index 7b6c107..fb4beab 100644 --- a/Sources/AloeStackView/Animation/AnimationCoordinator.swift +++ b/Sources/AloeStackView/Animation/AnimationCoordinator.swift @@ -16,9 +16,9 @@ import UIKit /** - * An object that animates when the Row is inserted and deleted from the AloeStackView. + * An object that animates when the Row is inserted and removed from the AloeStackView. */ -public class AnimationCoordinator { +internal class AnimationCoordinator { // MARK: Internal @@ -30,34 +30,52 @@ public class AnimationCoordinator { } internal func startAnimation() { - (target.contentView as? CustomAnimating)?.willAnimate(with: self) - UIView.animate(withDuration: AnimationCoordinator.defaultAnimationDuration, animations: { + let config = target.contentView as? CustomAnimating + + willBeginAnimation?() + + UIView.animate(withDuration: AnimationCoordinator.defaultAnimationDuration, + delay: 0, + usingSpringWithDamping: config?.springDamping ?? 1, + initialSpringVelocity: 0, + options: [.curveEaseInOut, .allowUserInteraction, .beginFromCurrentState], + animations: { + self.animate?() self.animations() - self.alongsideAnimation?() - }) { success in - self.completion?(success) - self.alongsideCompletion?(success) + }) { finished in + self.animationDidEnd?(finished) + self.completion?(finished) } } - // MARK: Public - - public let state: State - - /// AloeStackView gives you the opportunity to change state values ​​by taking over the animation behavior when Row is added and removed. - public func animate(alongsideAnimation animation: (() -> Void)?, completion: ((Bool) -> Void)? = nil) { - alongsideAnimation = animation - alongsideCompletion = completion - } // MARK: Private private let target: StackViewCell + private let state: State private let animations: () -> Void private let completion: ((Bool) -> Void)? - private var alongsideAnimation: (() -> Void)? - private var alongsideCompletion: ((Bool) -> Void)? + private lazy var willBeginAnimation: (() -> Void)? = { + switch state { + case .insert: return (target.contentView as? CustomAnimating)?.insertAnimationWillBegin + case .remove: return (target.contentView as? CustomAnimating)?.removeAnimationWillBegin + } + }() + + private lazy var animate: (() -> Void)? = { + switch state { + case .insert: return (target.contentView as? CustomAnimating)?.animateInsert + case .remove: return (target.contentView as? CustomAnimating)?.animateRemove + } + }() + + private lazy var animationDidEnd: ((Bool) -> Void)? = { + switch state { + case .insert: return (target.contentView as? CustomAnimating)?.insertAnimationDidEnd + case .remove: return (target.contentView as? CustomAnimating)?.removeAnimationDidEnd + } + }() private static let defaultAnimationDuration: TimeInterval = 0.3 diff --git a/Sources/AloeStackView/Animation/State.swift b/Sources/AloeStackView/Animation/State.swift index 6e05da4..13c412c 100644 --- a/Sources/AloeStackView/Animation/State.swift +++ b/Sources/AloeStackView/Animation/State.swift @@ -14,12 +14,12 @@ // limitations under the License. /** - * Indicates that the row is added and removed from the AloeStackView. + * Indicates that the row is inserted and removed from the AloeStackView. */ -public enum State { +internal enum State { case insert - case delete + case remove } diff --git a/Sources/AloeStackView/Protocols/CustomAnimating.swift b/Sources/AloeStackView/Protocols/CustomAnimating.swift index 66dabf9..2ce3da8 100644 --- a/Sources/AloeStackView/Protocols/CustomAnimating.swift +++ b/Sources/AloeStackView/Protocols/CustomAnimating.swift @@ -17,11 +17,55 @@ * The rows in `AloeStackView` help Custom Animation to work. * * Rows that are added to an `AloeStackView` can conform to this protocol to have their - * You can freely change the state animation of each contentView when a row is inserted or deleted. + * You can freely change the state animation of each contentView when a row is inserted or removed. */ + +import UIKit + public protocol CustomAnimating { - /// Invoked when animated is true when inserting or deleting. - func willAnimate(with coordinator: AnimationCoordinator) + /// The springDamping value used to determine the amount of 'bounce' + /// seen when animationing to insert or remove state + /// + /// Default Value is 0.8. + var springDamping: CGFloat { get } + + /// Called before insert animation is activated. + func insertAnimationWillBegin() + + /// Called when insert animation is working. + func animateInsert() + + /// Called after insert animation. + func insertAnimationDidEnd(_ finished: Bool) + + /// Called before remove animation is activated. + func removeAnimationWillBegin() + + /// Called when remove animation is working. + func animateRemove() + + /// Called after remove animation. + func removeAnimationDidEnd(_ finished: Bool) + +} + +public extension CustomAnimating where Self: UIView { + + var springDamping: CGFloat { + return 0.8 + } + + func animateInsert() { } + + func insertAnimationWillBegin() { } + + func insertAnimationDidEnd(_ finished: Bool) { } + + func animateRemove() { } + + func removeAnimationWillBegin() { } + + func removeAnimationDidEnd(_ finished: Bool) { } } From d910c999d01be22a8dcab9858e943c7f5d0f9d34 Mon Sep 17 00:00:00 2001 From: gwangbeom Date: Sun, 17 Mar 2019 21:21:47 +0900 Subject: [PATCH 8/9] Fix style --- .../Views/CustomAnimatingLabel.swift | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/Example/AloeStackViewExample/Views/CustomAnimatingLabel.swift b/Example/AloeStackViewExample/Views/CustomAnimatingLabel.swift index caafc42..c898e52 100644 --- a/Example/AloeStackViewExample/Views/CustomAnimatingLabel.swift +++ b/Example/AloeStackViewExample/Views/CustomAnimatingLabel.swift @@ -17,20 +17,20 @@ import UIKit import AloeStackView public class CustomAnimatingLabel: UILabel, CustomAnimating { - - public func animateInsert() { - transform = .identity - alpha = 1 - } - - public func insertAnimationWillBegin() { - transform = CGAffineTransform(translationX: -100, y: 0) - alpha = 0 - } - - public func animateRemove() { - transform = CGAffineTransform(translationX: -100, y: 0) - alpha = 0 - } + + public func animateInsert() { + transform = .identity + alpha = 1 + } + + public func insertAnimationWillBegin() { + transform = CGAffineTransform(translationX: -100, y: 0) + alpha = 0 + } + + public func animateRemove() { + transform = CGAffineTransform(translationX: -100, y: 0) + alpha = 0 + } } From b6592f61eeb9a1391a92aaa1c159daac884b9b42 Mon Sep 17 00:00:00 2001 From: Gwangbeom Date: Sun, 20 Oct 2019 00:31:18 +0900 Subject: [PATCH 9/9] fix code style --- .../Animation/AnimationCoordinator.swift | 22 +++++++++---------- .../Protocols/CustomAnimating.swift | 5 ++--- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/Sources/AloeStackView/Animation/AnimationCoordinator.swift b/Sources/AloeStackView/Animation/AnimationCoordinator.swift index fb4beab..f74fc1c 100644 --- a/Sources/AloeStackView/Animation/AnimationCoordinator.swift +++ b/Sources/AloeStackView/Animation/AnimationCoordinator.swift @@ -30,13 +30,10 @@ internal class AnimationCoordinator { } internal func startAnimation() { - let config = target.contentView as? CustomAnimating - willBeginAnimation?() - UIView.animate(withDuration: AnimationCoordinator.defaultAnimationDuration, delay: 0, - usingSpringWithDamping: config?.springDamping ?? 1, + usingSpringWithDamping: animatable?.springDamping ?? 1, initialSpringVelocity: 0, options: [.curveEaseInOut, .allowUserInteraction, .beginFromCurrentState], animations: { @@ -48,7 +45,6 @@ internal class AnimationCoordinator { } } - // MARK: Private private let target: StackViewCell @@ -56,24 +52,28 @@ internal class AnimationCoordinator { private let animations: () -> Void private let completion: ((Bool) -> Void)? + private var animatable: CustomAnimating? { + return target.contentView as? CustomAnimating + } + private lazy var willBeginAnimation: (() -> Void)? = { switch state { - case .insert: return (target.contentView as? CustomAnimating)?.insertAnimationWillBegin - case .remove: return (target.contentView as? CustomAnimating)?.removeAnimationWillBegin + case .insert: return animatable?.insertAnimationWillBegin + case .remove: return animatable?.removeAnimationWillBegin } }() private lazy var animate: (() -> Void)? = { switch state { - case .insert: return (target.contentView as? CustomAnimating)?.animateInsert - case .remove: return (target.contentView as? CustomAnimating)?.animateRemove + case .insert: return animatable?.animateInsert + case .remove: return animatable?.animateRemove } }() private lazy var animationDidEnd: ((Bool) -> Void)? = { switch state { - case .insert: return (target.contentView as? CustomAnimating)?.insertAnimationDidEnd - case .remove: return (target.contentView as? CustomAnimating)?.removeAnimationDidEnd + case .insert: return animatable?.insertAnimationDidEnd + case .remove: return animatable?.removeAnimationDidEnd } }() diff --git a/Sources/AloeStackView/Protocols/CustomAnimating.swift b/Sources/AloeStackView/Protocols/CustomAnimating.swift index 2ce3da8..93fc103 100644 --- a/Sources/AloeStackView/Protocols/CustomAnimating.swift +++ b/Sources/AloeStackView/Protocols/CustomAnimating.swift @@ -13,15 +13,14 @@ // See the License for the specific language governing permissions and // limitations under the License. +import UIKit + /** * The rows in `AloeStackView` help Custom Animation to work. * * Rows that are added to an `AloeStackView` can conform to this protocol to have their * You can freely change the state animation of each contentView when a row is inserted or removed. */ - -import UIKit - public protocol CustomAnimating { /// The springDamping value used to determine the amount of 'bounce'