diff --git a/Sources/AloeStackView/AloeStackView.swift b/Sources/AloeStackView/AloeStackView.swift index 07aa92f..7ce4830 100644 --- a/Sources/AloeStackView/AloeStackView.swift +++ b/Sources/AloeStackView/AloeStackView.swift @@ -64,35 +64,45 @@ open class AloeStackView: UIScrollView { /// Adds a row to the end of the stack view. /// /// If `animated` is `true`, the insertion is animated. - open func addRow(_ row: UIView, animated: Bool = false) { - insertCell(withContentView: row, atIndex: stackView.arrangedSubviews.count, animated: animated) + open func addRow(_ row: UIView, animated: Bool = false, completion: ((Bool) -> Void)? = nil) { + insertCell(withContentView: row, + atIndex: stackView.arrangedSubviews.count, + animated: animated, + completion: completion) } /// Adds multiple rows to the end of the stack view. /// /// If `animated` is `true`, the insertions are animated. - open func addRows(_ rows: [UIView], animated: Bool = false) { - rows.forEach { addRow($0, animated: animated) } + open func addRows(_ rows: [UIView], animated: Bool = false, completion: ((Bool) -> Void)? = nil) { + let group = DispatchGroup() + rows.forEach { group.enter(); addRow($0, animated: animated) { _ in group.leave() } } + group.notify(queue: .main) { completion?(true) } } /// Adds a row to the beginning of the stack view. /// /// If `animated` is `true`, the insertion is animated. - open func prependRow(_ row: UIView, animated: Bool = false) { - insertCell(withContentView: row, atIndex: 0, animated: animated) + open func prependRow(_ row: UIView, animated: Bool = false, completion: ((Bool) -> Void)? = nil) { + insertCell(withContentView: row, atIndex: 0, animated: animated, completion: completion) } /// Adds multiple rows to the beginning of the stack view. /// /// If `animated` is `true`, the insertions are animated. - open func prependRows(_ rows: [UIView], animated: Bool = false) { - rows.reversed().forEach { prependRow($0, animated: animated) } + open func prependRows(_ rows: [UIView], animated: Bool = false, completion: ((Bool) -> Void)? = nil) { + let group = DispatchGroup() + rows.reversed().forEach { group.enter(); prependRow($0, animated: animated) { _ in group.leave() } } + group.notify(queue: .main) { completion?(true) } } /// Inserts a row above the specified row in the stack view. /// /// If `animated` is `true`, the insertion is animated. - open func insertRow(_ row: UIView, before beforeRow: UIView, animated: Bool = false) { + open func insertRow(_ row: UIView, + before beforeRow: UIView, + animated: Bool = false, + completion: ((Bool) -> Void)? = nil) { #if swift(>=5.0) guard let cell = beforeRow.superview as? StackViewCell, @@ -102,20 +112,30 @@ open class AloeStackView: UIScrollView { let cell = beforeRow.superview as? StackViewCell, let index = stackView.arrangedSubviews.index(of: cell) else { return } #endif - insertCell(withContentView: row, atIndex: index, animated: animated) + insertCell(withContentView: row, atIndex: index, animated: animated, completion: completion) } /// Inserts multiple rows above the specified row in the stack view. /// /// If `animated` is `true`, the insertions are animated. - open func insertRows(_ rows: [UIView], before beforeRow: UIView, animated: Bool = false) { - rows.forEach { insertRow($0, before: beforeRow, animated: animated) } + open func insertRows(_ rows: [UIView], + before beforeRow: UIView, + animated: Bool = false, + completion: ((Bool) -> Void)? = nil) { + let group = DispatchGroup() + rows.forEach { + group.enter() + insertRow($0, before: beforeRow, animated: animated) { _ in group.leave() } } + group.notify(queue: .main) { completion?(true) } } /// Inserts a row below the specified row in the stack view. /// /// If `animated` is `true`, the insertion is animated. - open func insertRow(_ row: UIView, after afterRow: UIView, animated: Bool = false) { + open func insertRow(_ row: UIView, + after afterRow: UIView, + animated: Bool = false, + completion: ((Bool) -> Void)? = nil) { #if swift(>=5.0) guard let cell = afterRow.superview as? StackViewCell, @@ -125,44 +145,55 @@ open class AloeStackView: UIScrollView { let cell = afterRow.superview as? StackViewCell, let index = stackView.arrangedSubviews.index(of: cell) else { return } #endif - insertCell(withContentView: row, atIndex: index + 1, animated: animated) + insertCell(withContentView: row, atIndex: index + 1, animated: animated, completion: completion) } /// Inserts multiple rows below the specified row in the stack view. /// /// If `animated` is `true`, the insertions are animated. - open func insertRows(_ rows: [UIView], after afterRow: UIView, animated: Bool = false) { + open func insertRows(_ rows: [UIView], + after afterRow: UIView, + animated: Bool = false, + completion: ((Bool) -> Void)? = nil) { + let group = DispatchGroup() _ = rows.reduce(afterRow) { currentAfterRow, row in - insertRow(row, after: currentAfterRow, animated: animated) + group.enter() + insertRow(row, after: currentAfterRow, animated: animated) { _ in group.leave() } return row } + group.notify(queue: .main) { completion?(true) } } /// Removes the given row from the stack view. /// /// If `animated` is `true`, the removal is animated. - open func removeRow(_ row: UIView, animated: Bool = false) { + open func removeRow(_ row: UIView, animated: Bool = false, completion: ((Bool) -> Void)? = nil) { if let cell = row.superview as? StackViewCell { - removeCell(cell, animated: animated) + removeCell(cell, animated: animated, completion: completion) } } /// Removes the given rows from the stack view. /// /// If `animated` is `true`, the removals are animated. - open func removeRows(_ rows: [UIView], animated: Bool = false) { - rows.forEach { removeRow($0, animated: animated) } + open func removeRows(_ rows: [UIView], animated: Bool = false, completion: ((Bool) -> Void)? = nil) { + let group = DispatchGroup() + rows.forEach { group.enter(); removeRow($0, animated: animated) { _ in group.leave() } } + group.notify(queue: .main) { completion?(true) } } /// Removes all the rows in the stack view. /// /// If `animated` is `true`, the removals are animated. - open func removeAllRows(animated: Bool = false) { + open func removeAllRows(animated: Bool = false, completion: ((Bool) -> Void)? = nil) { + let group = DispatchGroup() stackView.arrangedSubviews.forEach { view in if let cell = view as? StackViewCell { - removeRow(cell.contentView, animated: animated) + group.enter() + removeRow(cell.contentView, animated: animated) { _ in group.leave() } } } + group.notify(queue: .main) { completion?(true) } } // MARK: Accessing Rows @@ -205,44 +236,52 @@ open class AloeStackView: UIScrollView { /// Hides the given row, making it invisible. /// /// If `animated` is `true`, the change is animated. - open func hideRow(_ row: UIView, animated: Bool = false) { - setRowHidden(row, isHidden: true, animated: animated) + open func hideRow(_ row: UIView, animated: Bool = false, completion: ((Bool) -> Void)? = nil) { + setRowHidden(row, isHidden: true, animated: animated, completion: completion) } /// Hides the given rows, making them invisible. /// /// If `animated` is `true`, the changes are animated. - open func hideRows(_ rows: [UIView], animated: Bool = false) { - rows.forEach { hideRow($0, animated: animated) } + open func hideRows(_ rows: [UIView], animated: Bool = false, completion: ((Bool) -> Void)? = nil) { + let group = DispatchGroup() + rows.forEach { group.enter(); hideRow($0, animated: animated) { _ in group.leave() } } + group.notify(queue: .main) { completion?(true) } } /// Shows the given row, making it visible. /// /// If `animated` is `true`, the change is animated. - open func showRow(_ row: UIView, animated: Bool = false) { - setRowHidden(row, isHidden: false, animated: animated) + open func showRow(_ row: UIView, animated: Bool = false, completion: ((Bool) -> Void)? = nil) { + setRowHidden(row, isHidden: false, animated: animated, completion: completion) } /// Shows the given rows, making them visible. /// /// If `animated` is `true`, the changes are animated. - open func showRows(_ rows: [UIView], animated: Bool = false) { - rows.forEach { showRow($0, animated: animated) } + open func showRows(_ rows: [UIView], animated: Bool = false, completion: ((Bool) -> Void)? = nil) { + let group = DispatchGroup() + rows.forEach { group.enter(); showRow($0, animated: animated) { _ in group.leave() } } + group.notify(queue: .main) { completion?(true) } } /// Hides the given row if `isHidden` is `true`, or shows the given row if `isHidden` is `false`. /// /// If `animated` is `true`, the change is animated. - open func setRowHidden(_ row: UIView, isHidden: Bool, animated: Bool = false) { + open func setRowHidden(_ row: UIView, + isHidden: Bool, + animated: Bool = false, + completion: ((Bool) -> Void)? = nil) { guard let cell = row.superview as? StackViewCell, cell.isHidden != isHidden else { return } if animated { - UIView.animate(withDuration: 0.3) { + UIView.animate(withDuration: 0.3, animations: { cell.isHidden = isHidden cell.layoutIfNeeded() - } + }, completion: completion) } else { cell.isHidden = isHidden + completion?(true) } } @@ -250,8 +289,15 @@ open class AloeStackView: UIScrollView { /// `false`. /// /// If `animated` is `true`, the change are animated. - open func setRowsHidden(_ rows: [UIView], isHidden: Bool, animated: Bool = false) { - rows.forEach { setRowHidden($0, isHidden: isHidden, animated: animated) } + open func setRowsHidden(_ rows: [UIView], + isHidden: Bool, + animated: Bool = false, + completion: ((Bool) -> Void)? = nil) { + let group = DispatchGroup() + rows.forEach { + group.enter() + setRowHidden($0, isHidden: isHidden, animated: animated) { _ in group.leave() } } + group.notify(queue: .main) { completion?(true) } } /// Returns `true` if the given row is hidden, `false` otherwise. @@ -529,7 +575,10 @@ open class AloeStackView: UIScrollView { return cell } - private func insertCell(withContentView contentView: UIView, atIndex index: Int, animated: Bool) { + private func insertCell(withContentView contentView: UIView, + atIndex index: Int, + animated: Bool, + completion: ((Bool) -> Void)? = nil) { let cellToRemove = containsRow(contentView) ? contentView.superview : nil let cell = createCell(withContentView: contentView) @@ -551,16 +600,18 @@ open class AloeStackView: UIScrollView { if animated { cell.alpha = 0 layoutIfNeeded() - UIView.animate(withDuration: 0.3) { + UIView.animate(withDuration: 0.3, animations: { cell.alpha = 1 - } + }, completion: completion) + } else { + completion?(true) } } - private func removeCell(_ cell: StackViewCell, animated: Bool) { + private func removeCell(_ cell: StackViewCell, animated: Bool, completion: ((Bool) -> Void)? = nil) { let aboveCell = cellAbove(cell: cell) - let completion: (Bool) -> Void = { [weak self] _ in + let completionHandler: (Bool) -> Void = { [weak self] finished in guard let `self` = self else { return } cell.removeFromSuperview() @@ -569,17 +620,16 @@ open class AloeStackView: UIScrollView { if let aboveCell = aboveCell { self.updateSeparatorVisibility(forCell: aboveCell) } + + completion?(finished) } if animated { - UIView.animate( - withDuration: 0.3, - animations: { - cell.isHidden = true - }, - completion: completion) + UIView.animate(withDuration: 0.3, animations: { + cell.isHidden = true + }, completion: completionHandler) } else { - completion(true) + completionHandler(true) } }