diff --git a/README.md b/README.md index e2849d6..7de0307 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,7 @@ You can think of it as `UITableView` but with several differences: | ⏱ | Compact code base, less than 1k LOC with no external dependencies. | | 🎯 | Easy to use and extensible APIs set. | | 🧬 | It uses standard UIKit components at its core. No magic, just a combination of `UIScrollView`+`UIStackView`. | +| 🧨 | Support SwiftUI's View and autosizing based upon View's content | | 🐦 | Fully made in Swift 5 from Swift ❥ lovers | ## ❤️ Your Support @@ -633,33 +634,37 @@ Example: ```swift class ViewController: ScrollStackController, ScrollStackControllerDelegate { - - func viewDidLoad() { - super.viewDidLoad() - - self.scrollStack.stackDelegate = self - } - - func scrollStackDidScroll(_ stackView: ScrollStack, offset: CGPoint) { - // stack did scroll - } - - func scrollStackRowDidBecomeVisible(_ stackView: ScrollStack, row: ScrollStackRow, index: Int, state: ScrollStack.RowVisibility) { - // Row did become partially or entirely visible. - } + + func viewDidLoad() { + super.viewDidLoad() + + self.scrollStack.stackDelegate = self + } + + func scrollStackDidScroll(_ stackView: ScrollStack, offset: CGPoint) { + // Stack did scroll + } - func scrollStackRowDidBecomeHidden(_ stackView: ScrollStack, row: ScrollStackRow, index: Int, state: ScrollStack.RowVisibility) { - // Row did become partially or entirely invisible. - } + + func scrollStackDidEndScrollingAnimation(_ stackView: ScrollStack) { + // Scrolling animation has ended + } - func scrollStackDidUpdateLayout(_ stackView: ScrollStack) { - // This function is called when layout is updated (added, removed, hide or show one or more rows). - } + func scrollStackRowDidBecomeVisible(_ stackView: ScrollStack, row: ScrollStackRow, index: Int, state: ScrollStack.RowVisibility) { + // Row did become partially or entirely visible. + } - func scrollStackContentSizeDidChange(_ stackView: ScrollStack, from oldValue: CGSize, to newValue: CGSize) { - // This function is called when content size of the stack did change (remove/add, hide/show rows). - } + func scrollStackRowDidBecomeHidden(_ stackView: ScrollStack, row: ScrollStackRow, index: Int, state: ScrollStack.RowVisibility) { + // Row did become partially or entirely invisible. + } + func scrollStackDidUpdateLayout(_ stackView: ScrollStack) { + // This function is called when layout is updated (added, removed, hide or show one or more rows). + } + + func scrollStackContentSizeDidChange(_ stackView: ScrollStack, from oldValue: CGSize, to newValue: CGSize) { + // This function is called when content size of the stack did change (remove/add, hide/show rows). + } } ``` diff --git a/ScrollStackController.podspec b/ScrollStackController.podspec index 00eb216..14b0645 100644 --- a/ScrollStackController.podspec +++ b/ScrollStackController.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "ScrollStackController" - s.version = "1.6.0" + s.version = "1.7.1" s.summary = "Create complex scrollable layout using UIViewController and simplify your code" s.homepage = "https://github.com/malcommac/ScrollStackController" s.license = { :type => "MIT", :file => "LICENSE" } @@ -10,5 +10,5 @@ Pod::Spec.new do |s| s.source = { :git => "https://github.com/malcommac/ScrollStackController.git", :tag => s.version.to_s } s.frameworks = "Foundation", "UIKit" s.source_files = 'Sources/**/*.swift' - s.swift_versions = ['5.0', '5.1', '5.3', '5.4', '5.5', '5.7', '5.8', '5.9'] + s.swift_versions = ['5.0', '5.1', '5.3', '5.4', '5.5', '5.7', '5.8', '5.9', '5.10'] end diff --git a/Sources/ScrollStackController/ScrollStack.swift b/Sources/ScrollStackController/ScrollStack.swift index 992f3a3..a8e3210 100644 --- a/Sources/ScrollStackController/ScrollStack.swift +++ b/Sources/ScrollStackController/ScrollStack.swift @@ -794,7 +794,13 @@ open class ScrollStack: UIScrollView, UIScrollViewDelegate { return .offscreen } - return (bounds.contains(rowFrame) ? .entire : .partial) + if bounds.contains(rowFrame) { + return .entire + } else { + let intersection = bounds.intersection(rowFrame) + let intersectionPercentage = ((intersection.width * intersection.height) / (rowFrame.width * rowFrame.height)) * 100 + return .partial(percentage: intersectionPercentage) + } } /// Remove passed row from stack view. @@ -860,6 +866,8 @@ open class ScrollStack: UIScrollView, UIScrollViewDelegate { return createRow(newRow, at: index, cellToRemove: cellToRemove, animated: animated, completion: completion) } + private var rowVisibilityChangesDispatchWorkItem: DispatchWorkItem? + /// Private implementation to add new row. private func createRow(_ newRow: ScrollStackRow, at index: Int, cellToRemove: ScrollStackRow?, @@ -878,7 +886,21 @@ open class ScrollStack: UIScrollView, UIScrollViewDelegate { }, completion: nil) } - scrollViewDidScroll(self) + if rowVisibilityChangesDispatchWorkItem == nil { + + rowVisibilityChangesDispatchWorkItem = DispatchWorkItem(block: { [weak self] in + if let stackDelegate = self?.stackDelegate { + self?.dispatchRowsVisibilityChangesTo(stackDelegate) + } + + self?.rowVisibilityChangesDispatchWorkItem = nil + }) + + /// Schedule a single `dispatchRowsVisibilityChangesTo(_:)` call. + /// + /// In this way, when rows are created inside a for-loop, the delegate is called only once after the `ScrollStack` has been fully laid out. + DispatchQueue.main.async(execute: rowVisibilityChangesDispatchWorkItem!) + } return newRow } @@ -979,25 +1001,27 @@ open class ScrollStack: UIScrollView, UIScrollViewDelegate { } private func dispatchRowsVisibilityChangesTo(_ delegate: ScrollStackControllerDelegate) { - delegate.scrollStackDidScroll(self, offset: contentOffset) - rows.enumerated().forEach { (idx, row) in let current = isRowVisible(index: idx) - if let previous = prevVisibilityState[row] { - switch (previous, current) { - case (.offscreen, .partial), // row will become invisible - (.hidden, .partial), - (.hidden, .entire): - delegate.scrollStackRowDidBecomeVisible(self, row: row, index: idx, state: current) - - case (.partial, .offscreen), // row will become visible - (.partial, .hidden), - (.entire, .hidden): - delegate.scrollStackRowDidBecomeHidden(self, row: row, index: idx, state: current) - - default: - break - } + let previous = prevVisibilityState[row] + + switch (previous, current) { + case (.offscreen, .partial), // row will become visible + (nil, .entire), + (nil, .partial), + (.partial, .entire), + (.hidden, .partial), + (.hidden, .entire): + delegate.scrollStackRowDidBecomeVisible(self, row: row, index: idx, state: current) + + case (.partial, .offscreen), // row will become invisible + (.entire, .partial), + (.partial, .hidden), + (.entire, .hidden): + delegate.scrollStackRowDidBecomeHidden(self, row: row, index: idx, state: current) + + default: + break } // store previous state @@ -1062,10 +1086,19 @@ open class ScrollStack: UIScrollView, UIScrollViewDelegate { guard let stackDelegate = stackDelegate else { return } - + stackDelegate.scrollStackDidScroll(self, offset: contentOffset) + dispatchRowsVisibilityChangesTo(stackDelegate) } + public func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) { + guard let stackDelegate = stackDelegate else { + return + } + + stackDelegate.scrollStackDidEndScrollingAnimation(self) + } + open override func layoutSubviews() { super.layoutSubviews() diff --git a/Sources/ScrollStackController/ScrollStackRow.swift b/Sources/ScrollStackController/ScrollStackRow.swift index 03be664..b334712 100644 --- a/Sources/ScrollStackController/ScrollStackRow.swift +++ b/Sources/ScrollStackController/ScrollStackRow.swift @@ -239,10 +239,11 @@ open class ScrollStackRow: UIView, UIGestureRecognizerDelegate { setNeedsUpdateConstraints() } - open override func layoutSubviews() { - super.layoutSubviews() + open override func updateConstraints() { // called the event to update the height of the row. askForCutomizedSizeOfContentView(animated: false) + + super.updateConstraints() } private func applyParentStackAttributes() { diff --git a/Sources/ScrollStackController/Support/ScrollStack+Protocols.swift b/Sources/ScrollStackController/Support/ScrollStack+Protocols.swift index 204ac98..aa3e11f 100644 --- a/Sources/ScrollStackController/Support/ScrollStack+Protocols.swift +++ b/Sources/ScrollStackController/Support/ScrollStack+Protocols.swift @@ -68,6 +68,11 @@ public protocol ScrollStackControllerDelegate: AnyObject { /// - Parameter offset: current scroll offset. func scrollStackDidScroll(_ stackView: ScrollStack, offset: CGPoint) + /// Tells the delegate when a scrolling animation in the scroll view concludes. + /// + /// - Parameter stackView: The ScrollStack object that’s performing the scrolling animation. + func scrollStackDidEndScrollingAnimation(_ stackView: ScrollStack) + /// Row did become partially or entirely visible. /// /// - Parameter row: target row. @@ -177,9 +182,9 @@ public extension ScrollStack { /// - `hidden`: row is invisible and hidden. /// - `offscreen`: row is not hidden but currently offscreen due to scroll position. /// - `removed`: row is removed manually. - enum RowVisibility { + enum RowVisibility: Equatable { case hidden - case partial + case partial(percentage: Double) case entire case offscreen case removed