@@ -241,7 +241,7 @@ open class ScrollStack: UIScrollView, UIScrollViewDelegate {
241241
242242 // MARK: - Set Rows
243243
244- /// Remove all existing rows and setup the new rows .
244+ /// Remove all existing rows and put in place the new list based upon passed controllers .
245245 ///
246246 /// - Parameter controllers: controllers to set.
247247 @discardableResult
@@ -250,7 +250,43 @@ open class ScrollStack: UIScrollView, UIScrollViewDelegate {
250250 return addRows ( controllers: controllers)
251251 }
252252
253+ /// Remove all existing rows and put in place the new list based upon passed views.
254+ ///
255+ /// - Parameter views: views to set.
256+ open func setRows( views: [ UIView ] ) -> [ ScrollStackRow ] {
257+ removeAllRows ( animated: false )
258+ return addRows ( views: views)
259+ }
260+
253261 // MARK: - Insert Rows
262+
263+ /// Insert a new to manage passed view without associated controller.
264+ ///
265+ /// - Parameters:
266+ /// - view: view to add. It will be added as contentView of the row.
267+ /// - location: location inside the stack of the new row.
268+ /// - animated: `true` to animate operation, by default is `false`.
269+ /// - completion: completion: optional completion callback to call at the end of insertion.
270+ open func addRow( view: UIView , at location: InsertLocation = . bottom, animated: Bool = false , completion: ( ( ) -> Void ) ? = nil ) -> ScrollStackRow ? {
271+ guard let index = indexForLocation ( location) else {
272+ return nil
273+ }
274+
275+ return createRowForView ( view, insertAt: index, animated: animated, completion: completion)
276+ }
277+
278+ /// Add new rows for each passed view.
279+ ///
280+ /// - Parameter controllers: controllers to add as rows.
281+ /// - Parameter location: location inside the stack of the new row.
282+ /// - Parameter animated: `true` to animate operatio, by default is `false`.
283+ open func addRows( views: [ UIView ] , at location: InsertLocation = . bottom, animated: Bool = false ) -> [ ScrollStackRow ] {
284+ enumerateItems ( views, insertAt: location) {
285+ addRow ( view: $0, at: location, animated: animated)
286+ }
287+ }
288+
289+
254290 /// Insert a new row to manage passed controller instance.
255291 ///
256292 /// - Parameter controller: controller to manage; it's `view` will be added as contentView of the row.
@@ -259,52 +295,22 @@ open class ScrollStack: UIScrollView, UIScrollViewDelegate {
259295 /// - Parameter completion: optional completion callback to call at the end of insertion.
260296 @discardableResult
261297 open func addRow( controller: UIViewController , at location: InsertLocation = . bottom, animated: Bool = false , completion: ( ( ) -> Void ) ? = nil ) -> ScrollStackRow ? {
262- switch location {
263- case . top:
264- return createRowForController ( controller, insertAt: 0 , animated: animated, completion: completion)
265-
266- case . bottom:
267- return createRowForController ( controller, insertAt: rows. count, animated: animated, completion: completion)
268-
269- case . atIndex( let index) :
270- return createRowForController ( controller, insertAt: index, animated: animated, completion: completion)
271-
272- case . after( let afterController) :
273- guard let index = rowForController ( afterController) ? . index else {
274- return nil
275- }
276-
277- let finalIndex = ( ( index + 1 ) >= rows. count ? rows. count : ( index + 1 ) )
278- return createRowForController ( controller, insertAt: finalIndex, animated: animated, completion: completion)
279-
280- case . before( let beforeController) :
281- guard let index = rowForController ( beforeController) ? . index else {
282- return nil
283- }
284-
285- return createRowForController ( controller, insertAt: index, animated: animated, completion: completion)
286-
298+ guard let index = indexForLocation ( location) else {
299+ return nil
287300 }
301+
302+ return createRowForController ( controller, insertAt: index, animated: animated, completion: completion)
288303 }
289304
290- /// Add new rows for each passaed controllers.
305+ /// Add new rows for each passed controllers.
291306 ///
292307 /// - Parameter controllers: controllers to add as rows.
293308 /// - Parameter location: location inside the stack of the new row.
294309 /// - Parameter animated: `true` to animate operatio, by default is `false`.
295310 @discardableResult
296311 open func addRows( controllers: [ UIViewController ] , at location: InsertLocation = . bottom, animated: Bool = false ) -> [ ScrollStackRow ] {
297- switch location {
298- case . top:
299- return controllers. reversed ( ) . compactMap ( {
300- addRow ( controller: $0, at: . top, animated: animated )
301- } ) . reversed ( ) // double reversed() is to avoid strange behaviour when additing rows on tops.
302-
303- default :
304- return controllers. compactMap {
305- addRow ( controller: $0, at: location, animated: animated)
306- }
307-
312+ enumerateItems ( controllers, insertAt: location) {
313+ addRow ( controller: $0, at: location, animated: animated)
308314 }
309315 }
310316
@@ -373,38 +379,32 @@ open class ScrollStack: UIScrollView, UIScrollViewDelegate {
373379 }
374380 }
375381
376- /// Replace an existing row with another new controller.
382+ /// Replace an existing row with another new row which manage passed view.
383+ ///
384+ /// - Parameters:
385+ /// - sourceIndex: row to replace.
386+ /// - view: view to use as `contentView` of the row.
387+ /// - animated: `true` to animate the transition.
388+ /// - completion: optional callback called at the end of the transition.
389+ open func replaceRow( index sourceIndex: Int , withRow view: UIView , animated: Bool = false , completion: ( ( ) -> Void ) ? = nil ) {
390+ doReplaceRow ( index: sourceIndex, createRow: { ( index, animated) -> ScrollStackRow in
391+ return self . createRowForView ( view, insertAt: index, animated: animated)
392+ } , animated: animated, completion: completion)
393+ }
394+
395+ /// Replace an existing row with another new row which manage passed controller.
377396 ///
378397 /// - Parameter row: row to replace.
379398 /// - Parameter controller: view controller to replace.
380399 /// - Parameter animated: `true` to animate the transition.
381400 /// - Parameter completion: optional callback called at the end of the transition.
382401 open func replaceRow( index sourceIndex: Int , withRow controller: UIViewController , animated: Bool = false , completion: ( ( ) -> Void ) ? = nil ) {
383- guard sourceIndex >= 0 , sourceIndex < rows. count else {
384- return
385- }
386-
387- let sourceRow = rows [ sourceIndex]
388-
389- guard animated else {
390- removeRow ( index: sourceRow. index!)
391- createRowForController ( controller, insertAt: sourceIndex, animated: false )
392- return
393- }
394-
395- stackView. setNeedsLayout ( )
396-
397- UIView . execute ( {
398- sourceRow. isHidden = true
399- } ) {
400- let newRow = self . createRowForController ( controller, insertAt: sourceIndex, animated: false )
401- newRow. isHidden = true
402- UIView . execute ( {
403- newRow. isHidden = false
404- } , completion: completion)
405- }
402+ doReplaceRow ( index: sourceIndex, createRow: { ( index, animated) in
403+ return self . createRowForController ( controller, insertAt: sourceIndex, animated: false )
404+ } , animated: animated, completion: completion)
406405 }
407406
407+
408408 /// Move the row at given index to another index.
409409 /// If one of the indexes is not valid nothing is made.
410410 ///
@@ -494,6 +494,19 @@ open class ScrollStack: UIScrollView, UIScrollViewDelegate {
494494 }
495495 }
496496
497+ /// Return the row associated with passed `UIView` instance and its index into the `rows` array.
498+ ///
499+ /// - Parameter view: target view (the `contentView` of the associated `ScrollStackRow` instance).
500+ open func rowForView( _ view: UIView ) -> ( index: Int , cell: ScrollStackRow ) ? {
501+ guard let index = rows. firstIndex ( where: {
502+ $0. contentView == view
503+ } ) else {
504+ return nil
505+ }
506+
507+ return ( index, rows [ index] )
508+ }
509+
497510 /// Return the row associated with passed `UIViewController` instance and its index into the `rows` array.
498511 ///
499512 /// - Parameter controller: target controller.
@@ -503,6 +516,7 @@ open class ScrollStack: UIScrollView, UIScrollViewDelegate {
503516 } ) else {
504517 return nil
505518 }
519+
506520 return ( index, rows [ index] )
507521 }
508522
@@ -626,6 +640,88 @@ open class ScrollStack: UIScrollView, UIScrollViewDelegate {
626640
627641 // MARK: - Private Functions
628642
643+ private func doReplaceRow( index sourceIndex: Int , createRow handler: @escaping ( ( Int , Bool ) -> ScrollStackRow ) , animated: Bool , completion: ( ( ) -> Void ) ? = nil ) {
644+ guard sourceIndex >= 0 , sourceIndex < rows. count else {
645+ return
646+ }
647+
648+ let sourceRow = rows [ sourceIndex]
649+ guard animated else {
650+ removeRow ( index: sourceRow. index!)
651+ _ = handler ( sourceIndex, false )
652+ return
653+ }
654+
655+ stackView. setNeedsLayout ( )
656+
657+ UIView . execute ( {
658+ sourceRow. isHidden = true
659+ } ) {
660+ let newRow = handler ( sourceIndex, false )
661+ newRow. isHidden = true
662+ UIView . execute ( {
663+ newRow. isHidden = false
664+ } , completion: completion)
665+ }
666+ }
667+
668+ /// Enumerate items to insert into the correct order based upon the location of destination.
669+ ///
670+ /// - Parameters:
671+ /// - list: list to enumerate.
672+ /// - location: insert location.
673+ /// - callback: callback to call on enumrate.
674+ private func enumerateItems< T> ( _ list: [ T ] , insertAt location: InsertLocation , callback: ( ( T ) -> ScrollStackRow ? ) ) -> [ ScrollStackRow ] {
675+ switch location {
676+ case . top:
677+ return list. reversed ( ) . compactMap ( callback) . reversed ( ) // double reversed() is to avoid strange behaviour when additing rows on tops.
678+
679+ default :
680+ return list. compactMap ( callback)
681+
682+ }
683+ }
684+
685+ /// Return the destination index for passed location. `nil` if index is not valid.
686+ ///
687+ /// - Parameter location: location.
688+ private func indexForLocation( _ location: InsertLocation ) -> Int ? {
689+ switch location {
690+ case . top:
691+ return 0
692+
693+ case . bottom:
694+ return rows. count
695+
696+ case . atIndex( let index) :
697+ return index
698+
699+ case . after( let controller) :
700+ guard let index = rowForController ( controller) ? . index else {
701+ return nil
702+ }
703+ return ( ( index + 1 ) >= rows. count ? rows. count : ( index + 1 ) )
704+
705+ case . afterView( let view) :
706+ guard let index = rowForView ( view) ? . index else {
707+ return nil
708+ }
709+ return ( ( index + 1 ) >= rows. count ? rows. count : ( index + 1 ) )
710+
711+ case . before( let controller) :
712+ guard let index = rowForController ( controller) ? . index else {
713+ return nil
714+ }
715+ return index
716+
717+ case . beforeView( let view) :
718+ guard let index = rowForView ( view) ? . index else {
719+ return nil
720+ }
721+ return index
722+ }
723+ }
724+
629725 /// Initial configuration of the control.
630726 private func setupUI( ) {
631727 backgroundColor = . white
@@ -730,6 +826,23 @@ open class ScrollStack: UIScrollView, UIScrollViewDelegate {
730826 return removedController
731827 }
732828
829+ /// Create a new row to handle passed view and insert it at specified index.
830+ ///
831+ /// - Parameters:
832+ /// - view: view to use as `contentView` of the row.
833+ /// - index: position of the new row with controller's view.
834+ /// - animated: `true` to animate transition.
835+ /// - completion: completion callback called when operation is finished.
836+ @discardableResult
837+ private func createRowForView( _ view: UIView , insertAt index: Int , animated: Bool , completion: ( ( ) -> Void ) ? = nil ) -> ScrollStackRow {
838+ // Identify any other cell with the same controller
839+ let cellToRemove = rowForView ( view) ? . cell
840+
841+ // Create the new container cell for this view.
842+ let newRow = ScrollStackRow ( view: view, stackView: self )
843+ return createRow ( newRow, at: index, cellToRemove: cellToRemove, animated: animated, completion: completion)
844+ }
845+
733846 /// Create a new row to handle passed controller and insert it at specified index.
734847 ///
735848 /// - Parameter controller: controller to manage.
@@ -743,9 +856,16 @@ open class ScrollStack: UIScrollView, UIScrollViewDelegate {
743856
744857 // Create the new container cell for this controller's view
745858 let newRow = ScrollStackRow ( controller: controller, stackView: self )
859+ return createRow ( newRow, at: index, cellToRemove: cellToRemove, animated: animated, completion: completion)
860+ }
861+
862+ /// Private implementation to add new row.
863+ private func createRow( _ newRow: ScrollStackRow , at index: Int ,
864+ cellToRemove: ScrollStackRow ? ,
865+ animated: Bool , completion: ( ( ) -> Void ) ? = nil ) -> ScrollStackRow {
746866 onChangeRow ? ( newRow, false )
747867 stackView. insertArrangedSubview ( newRow, at: index)
748-
868+
749869 // Remove any duplicate cell with the same view
750870 removeRowFromStackView ( cellToRemove)
751871
0 commit comments