🧩 Easy scrollable layouts in UIKit

Overview

ScrollStackController

Easy scrollable layouts in UIKit

Create complex scrollable layout using UIViewControllers or plain UIViews and simplify your code!

ScrollStackController is a class you can use to create complex layouts using scrollable UIStackView but where each row is handled by a separate UIViewController; this allows you to keep a great separation of concerns.

You can think of it as UITableView but with several differences:

  • Each row can a different UIViewController you can manage independently: no more massive controllers, a much cleaner and maintainable architecture.
  • You can still use plain UIView instances if need a lightweight solution: this is especially useful when you are using ScrollStackController as layout-helper or your view don't have a complex logic and you can still use the main controller.
  • Powered by AutoLayout since the beginning; it uses a combination of UIScrollView + UIStackView to offer an animation friendly controller ideal for fixed and dynamic row sizing.
  • You don't need to struggle yourself with view recycling: suppose you have a layout composed by several different screens. There is no need of view recycling but it cause a more difficult managment of the layout. With a simpler and safer APIs set ScrollStackView is the ideal way to implement such layouts.
Features Highlights
πŸ•Ί Create complex layout without the boilerplate required by view recyling of UICollectionView or UITableView.
🧩 Simplify your architecture by thinking each screen as a separate-indipendent UIVIewController.
🧩 Support for lightweight mode to layout UIView without UIViewController.
🌈 Animate show/hide and resize of rows easily even with custom animations!
⏱ 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.
🐦 Fully made in Swift 5 from Swift β₯ lovers

❀️ Your Support

Hi fellow developer!
You know, maintaing and developing tools consumes resources and time. While I enjoy making them your support is foundamental to allow me continue its development.

If you are using SwiftLocation or any other of my creations please consider the following options:

Table of Contents

When to use ScrollStackController and when not

ScrollStackController is best used for shorter screens with an heterogeneous set of rows: in these cases you don't need to have view recycling.

Thanks to autolayout you will get updates and animations for free.

You can also manage each screen independently with a great separation of concerns; morehover unlike UITableView and UICollectionView, you can keep strong references to UIViewController (and its views) in an ScrollStack view and make changes to them at any point.

ScrollStackController is not suitable in all situations. ScrollStackController lays out the entire UI at first time when your screen loads. If you have a long list of rows you may experience delays.

So, ScrollStackController is generally not appropriate for screens that contain many views of the same type, all showing similar data (in these cases you should use UITableView or UICollectionView).

Demo Project

↑ Back To Top

How to use it

The main class of the package is ScrollStack, a subclass of UIScrollView. It manages the layout of each row, animations and keep a strong reference to your rows.

This is an overview of the architecture:

  • ScrollStackController : is a subclass of UIViewController. You would to use it and add as a child controller of your view controller. This allows you to manage any child-controllers related events for each row you will add to the stack controller.
  • ScrollStack: the view of the ScrollStackController is a ScrollStack, a subclass of UIScrollView with an UIStackView which allows you to manage the layout of the stack. You can access to it via scrollStack property of the controller.
  • Each row is a ScrollStackRow, which is a subclass of UIView. Inside there are two views, the contentView (a reference to managed UIViewController's view) and the separatorView. A row strongly reference managed view controller, so you don't need to keep a strong reference by your own.
  • Separator view are subclass of ScrollStackSeparator class.

As we said, usually you don't want to intantiate a ScrollStack control directly but by using the ScrollStackController class. It's a view controller which allows you to get the child view controller's managment for free, so when you add/remove a row to the stack you will get the standard UIViewController events for free!

This is an example of initialization in a view controller:

class MyViewController: UIViewController {

    private var stackController = ScrollStackViewController()

    override func viewDidLoad() {
        super.viewDidLoad()
        
        stackController.view.frame = contentView.bounds
        contentView.addSubview(stackController.view)
    }
    
}

Now you are ready to use the ScrollStack control inside the stackController class. ScrollStack have an extensible rich set of APIs to manage your layout: add, remove, move, hide or show your rows, including insets and separator management.

Each row managed by ScrollStack is a subclass of ScrollStackRow: it strongly reference a parent UIViewController class where you content is placed. UIViewController's view will be the contentView of the row itself.

You don't need to handle lifecycle of your rows/view controller until they are part of the rows inside the stack.

To get the list of rows of the stack you can use the rows property.

// Standard methods
let allRows = scrollStack.rows
let isEmpty = scrollStack.isEmpty // true if it does not contains row
let notHiddenRows = scrollStack.rows.filter { !$0.isHidden }

// By Visibility
let currentlyVisibleRows = scrollStack.visibleRows // only currently visible rows (partially or enterly)
let enterlyVisibleRows = scrollStack.enterlyVisibleRows // only enterly visible rows into the stack

// Shortcuts
let firstRow = scrollStack.firstRow
let lastRow = scrollStack.lastRow

Let's take a look below.

↑ Back To Top

Adding Rows

ScrollStack provides a comprehensive set of methods for managing rows, including inserting rows at the beginning and end, inserting rows above or below other rows.

To add row you can use one the following methods:

  • addRow(controller:at:animated:) -> ScrollStackRow?
  • addRows(controllers:at:animated:) -> [ScrollStackRow]?

Both of these methods takes as arguments:

  • controller/s: one or more UIViewController instances; each view of these controllers will be as a row of the stack inside a ScrollStackRow (a sort of cell).
  • at: specify the insertion point. It's an enum with the following options: top (at first index), bottom (append at the bottom of the list), atIndex (specific index), after or below (after/below a row which contain a specific UIViewController).
  • animated: if true insertion will be animated
  • completion: completion callback to call at the end of the operation.

The following code add a rows with the view of each view controller passed:

   let welcomeVC = WelcomeVC.create()
   let tagsVC = TagsVC.create(delegate: self)
   let galleryVC = GalleryVC.create()
        
   stackView.addRows(controllers: [welcomeVC, notesVC, tagsVC, galleryVC], animated: false)

As you noticed there is not need to keep a strong reference to any view controller; they are automatically strong referenced by each row created to add them into the stack.

↑ Back To Top

Removing / Replacing Rows

A similar set of APIs are used to remove existing rows from the stack:

  • removeAllRows(animated:): to remove all rows of the stack.
  • removeRow(index:animated:) -> UIViewController?: to remove a specific row at given index. It returns a reference to removed view controller.
  • removeRows(indexes:animated:) -> [UIViewController]?: to remove rows at specified indexes from the stack. Removed managed UIViewController instances are returned.
  • replaceRow(index:withRow:animated:completion:): replace an existing row with a new row which manage new passed view controller.

An example:

let newVC: UIViewController = ...
stackView.replaceRow(index: 1, withRow: newVC, animated: true) {
	print("Gallery controller is now in place!!")
}

↑ Back To Top

Move Rows

If you need to adjust the hierarchy of the stack by moving a row from a position to another you can use:

  • moveRow(index:to:animated:completion:): move a row at passed inside to another index (both of indexes must be valid).

The following method move the first row at a random position, by animating the transition:

let randomDst = Int.random(in: 1..<stackView.rows.count)
stackView.moveRow(index: 0, to: randomDst, animated: true, completion: nil)

↑ Back To Top

Hide / Show Rows

ScrollStack uses the power of UIStackView: you can show and hide rows easily with a gorgeous animation by using one of the following methods:

  • setRowHidden(index:isHidden:animated:completion:): hide or show a row at index.
  • setRowsHidden(indexes:isHidden:animated:completion:): hide or show multiple rows at specified indexes.

Example:

stackView.setRowsHidden(indexes: [0,1,2], isHidden: true, animated: true)

Keep in mind: when you hide a rows the row still part of the stack and it's not removed, just hidden! If you get the list of rows by calling rows property of the ScrollStack you still see it.

↑ Back To Top

Hide / Show Rows with custom animations

You can easily show or hide rows with any custom transition; your view controller just need to be conform to the ScrollStackRowAnimatable protocol.
This protocol defines a set of animation infos (duration, delay, spring etc.) and two events you can override to perform actions:

public protocol ScrollStackRowAnimatable {
    /// Animation main info.
    var animationInfo: ScrollStackAnimationInfo { get }
    
    /// Animation will start to hide or show the row.
    func willBeginAnimationTransition(toHide: Bool)
    
    /// Animation to hide/show the row did end.
    func didEndAnimationTransition(toHide: Bool)
    
    /// Animation transition.
    func animateTransition(toHide: Bool)
}

So for example you can replicate the following animation:

by using the following code:

extension WelcomeVC: ScrollStackRowAnimatable {
    public var animationInfo: ScrollStackAnimationInfo {
        return ScrollStackAnimationInfo(duration: 1, delay: 0, springDamping: 0.8)
    }

    public func animateTransition(toHide: Bool) {
        switch toHide {
            case true:
                self.view.transform = CGAffineTransform(translationX: -100, y: 0)
                self.view.alpha = 0
            
            case false:
                self.view.transform = .identity
                self.view.alpha = 1
        }
    }
    
    public func willBeginAnimationTransition(toHide: Bool) {
        if toHide == false {
            self.view.transform = CGAffineTransform(translationX: -100, y: 0)
            self.view.alpha = 0
        }
    }
    
}

Reload Rows

Reload rows method allows you to refresh the layout of the entire stack (using layoutIfNeeded()) while you have a chance to update a specific row's contentView (aka the view of the managed UIViewController).

There are three methods:

  • reloadRow(index:animated:completion:): reload a specific row at index.
  • reloadRows(indexes:animated:completion:): reload a specific set of rows.
  • reloadAllRows(animated:completion:): reload all rows.

If your UIViewController implements ScrollStackContainableController protocol you will get notified inside the class about this request, so you have the opportunity to refresh your data:

Example:

class MyViewController: UIViewController {

	private let scrollStackController = ScrollStackController()
	
	@IBAction func someAction() {
		scrollStackController.scrollStack.reloadRow(0)
	}

}

// Your row 0 manages the GalleryVC, so in your GalleryVC implementation:

class GalleryVC: UIViewController, ScrollStackContainableController {

    public func func reloadContentFromStackView(stackView: ScrollStack, row: ScrollStackRow, animated: Bool) {
		// update your UI
	}
	
}

↑ Back To Top

Sizing Rows

You can control the size of your UIViewController inside a row of a ScrollStack in two ways:

  • Creating contrains in your UIViewController's view with Autolayout.
  • Implementing ScrollStackContainableController protocol in your UIViewController class and return a non nil value in scrollStackRowSizeForAxis(:row:in:) -> ScrollStack.ControllerSize? delegate method.

In both case ScrollStack class will use only one dimension depending by the active scroll axis to layout the view controller content into the stack (if scroll axis is horizontal you can control only the height of the row, if it's vertical only the width. The other dimension will be the same of the scroll stack itself.

Each of the following cases is covered inside the demo application:

↑ Back To Top

Fixed Row Size

If your view controller has a fixed size you can just return it as follows:

class GalleryVC: UIViewController, ScrollStackContainableController {

    public func scrollStackRowSizeForAxis(_ axis: NSLayoutConstraint.Axis, row: ScrollStackRow, in stackView: ScrollStack) -> ScrollStack.ControllerSize? {
    	switch axis {
    	case .horizontal:
       	  return .fixed(300)
      	case .vertical:
       	  return .fixed(500)
       }
    }
    
}

If your stack support single axis you can obivously avoid switch condition. When you will add this view controller in a scroll stack it will be sized as you requested (any height/width constraint already in place will be removed).

↑ Back To Top

Fitting Layout Row Size

Sometimes you may want to have the content view sized by fitting the contents of the view controller's view. In these cases you can use . fitLayoutForAxis.

Example:

public func scrollStackRowSizeForAxis(_ axis: NSLayoutConstraint.Axis, row: ScrollStackRow, in stackView: ScrollStack) -> ScrollStack.ControllerSize? {
	return .fitLayoutForAxis
}

ScrollStack will use the systemLayoutSizeFitting() method on your view controller's view to get the best size to fit the content.

↑ Back To Top

Collapsible Rows

Sometimes you may want to create collapsible rows. These row can have different heights depending of a variable.

In this case you just need to implement a isExpanded: Bool variable in your view controller and return a different height based on it.

public class TagsVC: UIViewController, ScrollStackContainableController {

    public var isExpanded = false
    
    public func scrollStackRowSizeForAxis(_ axis: NSLayoutConstraint.Axis, row: ScrollStackRow, in stackView: ScrollStack) -> ScrollStack.ControllerSize? {
        return (isExpanded == false ? .fixed(170) : .fixed(170 + collectionView.contentSize.height + 20))
    }
}

In your main view controller you may call this:

	 // get the first row which manages this controller
	 let tagsRow = stackView.firstRowForControllerOfType(TagsVC.self)
	 // or if you have already the instance you can get the row directly
	 // let tagsRow = stackView.rowForController(tagsVCInstance)
	 
	 let tagsVCInstance = (tagsRow.controller as! TagsVC)
	 tagsVCInstance.isExpanded = !tagsVCInstance.isExpanded
	 
	 stackView.reloadRow(tagsRow, animated: true)

And your rows will perform a great animation to resize its content.

↑ Back To Top

Working with dynamic UICollectionView/UITableView/UITextView

There are some special cases where you may need to resize the row according to the changing content in your view controller's view.

Consider for example an UIViewController with a UITableView inside; you may want to show the entire table content's as it grown. In this case you need to make some further changes:

  • You need to return .fitLayoutForAxis.
  • In your view controller's view you need to create a reference to the height constraint of your table.
  • You need to create a constraint from the table to the bottom safe area of your view (this will be used by AL to grow the size of the view).

Then you must override the updateViewConstraints() to change the value of the table's height constraint to the right value.

This is the code:

public class PricingVC: UIViewController, ScrollStackContainableController {
    
    public weak var delegate: PricingVCProtocol?
    
    @IBOutlet public var pricingTable: UITableView!
    @IBOutlet public var pricingTableHeightConstraint: NSLayoutConstraint!
    
    public func scrollStackRowSizeForAxis(_ axis: NSLayoutConstraint.Axis, row: ScrollStackRow, in stackView: ScrollStack) -> ScrollStack.ControllerSize? {
        return .fitLayoutForAxis
    }
    
    override public func updateViewConstraints() {
        pricingTableHeightConstraint.constant = pricingTable.contentSize.height // the size of the table as the size of its content
        view.height(constant: nil) // cancel any height constraint already in place in the view
        super.updateViewConstraints()
    }
}

In this way as you add new value to the table the size of the row in stack view will grown.

↑ Back To Top

Rows Separator

Each row managed by ScrollStack is of a subview class of type ScrollStackRow. It has a strong referenced to managed UIViewController but also have a subview on bottom called ScrollStackSeparator.

You can hide/show separators by using the following properties of the row:

  • isSeparatorHidden: to hide separator.
  • separatorInsets: to set the insets of the sepatator (by default is set to the same value used by UITableView instances)
  • separatorView.color: to change the color
  • separatorView.thickness: to se the thickness of the separator (1 by default).

Moreover you can set these values directly on ScrollStack controller in order to have a default value for each new row.

ScrollStack also have a property called autoHideLastRowSeparator to hide the last separator of the stack automatically.

↑ Back To Top

Using plain UIViews instead of view controllers

Since version 1.3.x ScrollStack can also be used to layout plain UIView instances which not belong to a parent view controllers.
This is especially useful when you don't have a complex logic in your views and you want to use ScrollStack to make custom layout and keep your code lightweight.

Using plain views is pretty easy; each row method supports both UIView or UIViewController as parameter.

Since you are working with plain UIView instances in order to size it correctly you must set its heightAnchor or widthAncor (depending of your stack orientation) before adding it to the stack. As for controllers, ScrollStack keeps a strong reference to the managed view which is added as contentView of the parent ScrollStackRow instance as it happens for UIViewController's .view property.

This is a small example:

let myCustomView = UIView(frame: .zero)
myCustomView.backgroundColor = .green
myCustomView.heightAnchor.constraint(equalToConstant: 300).isActive = true
stackView.addRow(view: myCustomView)

Tap On Rows

By default rows are not tappable but if you need to implement some sort of tap features like in UITableView you can add it by setting a default callback for onTap property on ScrollStackRow instances.

For example:

scrollStack.firstRow?.onTap = { row in
	// do something on tap
}

Once you can set a tap handler you can also provide highlight color for tap. To do it you must implement ScrollStackRowHighlightable protocol in your row managed view controller.

For example:

class GalleryVC: UIViewController, ScrollStackRowHighlightable {

    public var isHighlightable: Bool {
    	return true
    }
    
    func setIsHighlighted(_ isHighlighted: Bool) {
    	self.view.backgroundColor = (isHighlighted ? .red : .white)
    }

}

Transition between highlights state will be animated automatically.

↑ Back To Top

Get the row/controller

Get the (first) row which manage a specific view controller type You can get the first row which manage a specific view controller class using firstRowForControllerOfType<T: UIViewController>(:) -> ScrollStackRow? function.

let tagsVC = scrollStack.firstRowForControllerOfType(TagsVC.self) // TagsVC instance

Get the row which manage a specific controller instance To get the row associated with a specific controller you can use rowForController() function:

let row = scrollStack.rowForController(tagsVC) // ScrollStackRow

Set Row Insets

To set an insets for a specific row you can use setRowInsets() function:

let newInsets: UIEdgeInsets = ...
scrollStack.setRowInsets(index: 0, insets: newInsets)

You can also use setRowsInsets() to set multiple rows.

Moreover by setting .rowInsets in your ScrollStack class you can set a default insets value for new row added.

Change ScrollStack scrolling axis

In order to change the axis of scroll for your ScrollStack instances you can set the axis property to horizontal or `vertical.

Subscribe to Row Events

You can listen when a row is removed or added into the stack view by subscribing the onChangeRow property.

scrollStackView.onChangeRow = { (row, isRemoved) in
  if isRemoved {
    print("Row at index \(row.index) was removed"
  } else {
    print("A new row is added at index: \(row.index). It manages \(type(of: row.controller))")
  }
}

You can also subscribe events for events about row visibility state changes by setting the stackDelegate. Your destination object must therefore conforms to the ScrollStackControllerDelegate protocol:

Example:

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 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).
  }

}

ScrollStack.RowVisibility is an enum with the following cases:

  • partial: row is partially visible.
  • entire: row is entirely visible.
  • hidden: row is invisible and hidden.
  • offscreen: row is not hidden but currently offscreen due to scroll position.

↑ Back To Top

System Requirements

  • iOS 11+
  • Xcode 10+
  • Swift 5+

↑ Back To Top

Example App

ScrollStackController comes with a demo application which show how easy you can create complex scrollable layoyut and some of the major features of the library.

You should look at it in order to implement your own layout, create dynamically sized rows and dispatch events.

↑ Back To Top

Installation

ScrollStackController can be installed with CocoaPods by adding pod 'ScrollStackController' to your Podfile.

pod 'ScrollStackController'

It also supports Swift Package Maneger aka SPM in your Package.swift:

import PackageDescription

  let package = Package(name: "YourPackage",
    dependencies: [
      .Package(url: "https://github.com/malcommac/ScrollStackController.git", majorVersion: 0),
    ]
  )

↑ Back To Top

Consider ❀️ support the development of this library!

Contributing

  • If you need help or you'd like to ask a general question, open an issue.
  • If you found a bug, open an issue.
  • If you have a feature request, open an issue.
  • If you want to contribute, submit a pull request.

Copyright & Acknowledgements

ScrollStackController is currently owned and maintained by Daniele Margutti.
You can follow me on Twitter @danielemargutti.
My web site is https://www.danielemargutti.com

This software is licensed under MIT License.

Follow me on:

Comments
  • Demo project is broken

    Demo project is broken

    When building and running the demo project on Xcode 12.0 and iOS 13.5 or iOS 14.0:

    1. The Xcode debug console is filled with "Unable to simultaneously satisfy constraints" messages
    2. None of the child view controllers are shown and the six buttons at the bottom of the view don't work It looks like this was broken by the v1.4.1 release

    ios14-xcode12

    bug 
    opened by graemesi 3
  • Swift Package Manager generated Xcode project fails to compile with minimum target compiler

    Swift Package Manager generated Xcode project fails to compile with minimum target compiler

    What did you do?

    • Added ScrollStackController to my project via Swift Package Manager
    • Tried to compile project (Minimum target iOS 14.0)

    What did you expect to happen?

    Compiles without errors

    What happened instead?

    Got the following compile error on this line:

    Screen Shot 2021-04-01 at 8 41 08 PM

    Proposed Solution

    Adding a dependency to the Package.swift file, in order to enforce the minimum iOS version, iOS 11.0

    platforms: [.iOS(.v11)],
    
    opened by eytanschulman 1
  • Support for lightweight rows with plain UIView (without associated UIViewControllers)

    Support for lightweight rows with plain UIView (without associated UIViewControllers)

    Sometimes creating a view controller to manage a view is a bit overkill, especially if your view does not contains too much business logic. In these case maybe useful to support just views without associated controllers.

    All methods to add, replace, remove and query row should also support views as parameter, the row must have optional controller property.

    enhancement 
    opened by malcommac 1
  • Fixed controller's `contentView` autosizing when reusing a controller previously set as fixed-dimension controller

    Fixed controller's `contentView` autosizing when reusing a controller previously set as fixed-dimension controller

    When you are adding a controller to the stack setting the size as fixed the library add an height/width constant constraint to setup desired dimension.
    If you plan to reuse the controller instance or change the sizing from fixed to automatic dimension, applied constraint is kept in place and the autosizing is broken returning the wrong size for the contentView of the associated ScrollStackRow instance:

    The solution is to remove any height/width constant constraints before re-adding the controller to the stack:

        private func removeFixedDimensionConstraintsIfNeeded(_ contentView: UIView) {
            let fixedDimensions = contentView.constraints.filter({
                $0.firstAttribute == .height || $0.firstAttribute == .width
            })
            contentView.removeConstraints(fixedDimensions)
        }
    

    The method above is called when evaluating the dynamic size of the controller's contentView inside setupRowSizeToFitLayout() method of the ScrollStackRow.swift:

    private func setupRowSizeToFitLayout()  {
            guard let stackView = stackView, let contentView = contentView else { return }
            
            // If user changed the way of how the controller's view is resized
            // (ie from fixed height to auto-dimension) we should need to remove
            // attached constraints about height/width in order to leave the viww to
            // auto resize according to its content.
            removeFixedDimensionConstraintsIfNeeded(contentView)
            
            var bestSize: CGSize!
            if stackView.axis == .vertical {
                let maxAllowedSize = CGSize(width: stackView.bounds.size.width, height: CGFloat.greatestFiniteMagnitude)
                bestSize = contentView.systemLayoutSizeFitting(maxAllowedSize, withHorizontalFittingPriority: .required, verticalFittingPriority: .defaultLow)
            } else {
                let maxAllowedSize = CGSize(width: CGFloat.greatestFiniteMagnitude, height: stackView.bounds.size.height)
                bestSize = contentView.systemLayoutSizeFitting(maxAllowedSize, withHorizontalFittingPriority: .defaultLow, verticalFittingPriority: .required)
            }
            
            setupRowToFixedValue(bestSize.height)
        }
    

    This solves the issue. It will be applied in 1.42.

    bug 
    opened by malcommac 0
  • @discardableResult in setRows/addRow/addRows, remove .swiftpm, cleanup of Package.swift

    @discardableResult in setRows/addRow/addRows, remove .swiftpm, cleanup of Package.swift

    Hi Daniele πŸ‘‹

    This PR makes a few improvements

    1. There was no scheme in Xcode when opening the package. This cleans up the Package.swift file.
    2. Extends the .gitignore file to permanently remove the unnecessary folder .swiftpm.
    3. Adds missing @discardableResult to three functions setRows, addRow and addRows.

    Cheers, Roman

    enhancement 
    opened by rmnblm 0
  • Added minimum iOS version to SPM description

    Added minimum iOS version to SPM description

    Resolves this issue https://github.com/malcommac/ScrollStackController/issues/17

    Tested within a project.

    What tests were performed?

    • Added ScrollStackController as a Swift Package.
    • Compiled project, which produced compiler errors, as detailed in the issue.
    • Removed ScrollStackController, and added this fork, in which the solution is included.
    • Compiled project, with no compiler errors.
    bug 
    opened by eytanschulman 0
  • Cannot update the height of the ScrollStackRow's contentView because it does not receive scrollStackRowSizeForAxis() event when its layout di change

    Cannot update the height of the ScrollStackRow's contentView because it does not receive scrollStackRowSizeForAxis() event when its layout di change

    Once placed the contentView (custom view or controller's view does not matter) for a ScrollStackRow cannot receive further scrollStackRowSizeForAxis() of the ScrollStackContainableController protocol and cannot correctly resize its content.

    This is especially true when you need to make a row's height equal to the content of the inner UITableView instance evaluated using autolayout. If the contentView is placed the height is calculated with a wrong size which is the not the real size of the table placed but the table loaded initially from nib.

    This fix allows to call this method when layoutSubviews of the row is called.

        open override func layoutSubviews() {
            super.layoutSubviews()
            
            askForCutomizedSizeOfContentView(animated: false)
        }
    
    bug 
    opened by malcommac 0
  • Separator visibility is wrongly updated when setRows/addRows is called

    Separator visibility is wrongly updated when setRows/addRows is called

    Separator visibility call is executed for each cycle in setRows()/addRows() causing visibility change for each row which is wrongly identified as the last row during the nth iteration.

    Moreover the isSeparatorHidden of the ScrollStackRows is strictly linked the the separator view while must be an independent property which is maintained regardless the visibility status when its the last row of the list.

    bug 
    opened by malcommac 0
  • ScrollStackRow and associated controller are not deallocated once removed

    ScrollStackRow and associated controller are not deallocated once removed

    There are several leaks:

    • Using removeRow() family methods to remove one or multiple rows does not deallocate associated controller and the row itself.
    • Moreover removeArrangedSubview() is not called to remove the view from the stackview along with classic removeSuperview().
    • Finally the prevVisibilityState of the ScrollStackRow class keep alive the row once removed.
    bug 
    opened by malcommac 0
  • reloadContentFromStackView() should be called before getting the new size of the row (askForCutomizedSizeOfContentView())

    reloadContentFromStackView() should be called before getting the new size of the row (askForCutomizedSizeOfContentView())

    When using reload function of row controller the order of operations should be:

    • ask to controller to reload the content of the row
    • ask to controller the new size of the row based upon the updated content

    Actually this operation is made in ScrollStack.swift here:

    rows.forEach {
      $0.askForCutomizedSizeOfContentView(animated: animated)
      ($0.controller as? ScrollStackContainableController)?.reloadContentFromStackView(stackView: self, row: $0, animated: animated)
     }
    

    but it's made in the wrong order causing troubles getting the right size for views and lots of layoutIfNeeded calls to adjust the wrong sizing evaluation.

    bug enhancement 
    opened by malcommac 0
  • rowPadding layoutIfNeeded causes errors in autolayout

    rowPadding layoutIfNeeded causes errors in autolayout

    explicit call to layoutIfNeeded() in didSet of ScrollStackRow'srowPadding should be avoided because it's not necessary and generate lots of errors in autolayout while is in processing.

    bug 
    opened by malcommac 0
  • UICollectionView inside child view controller does not reuse cell properly

    UICollectionView inside child view controller does not reuse cell properly

    I have created a simple UIViewController which contains a UICollectionView display a list of 10 object. The UICollectionView has a flow layout with vertical direction.

    When putting the child VC into the ScrollStackController, the Allocation instrument of xcode tells me that all 10 instances of that cell class are created. If I just add a single child VC directly to the parent VC, UICollectionView will reuse properly, which means only 4-5 instances of UICollectionView are created according to Allocation instrument.

    Is this an expected behavior with the library or I am just messing something up?

    opened by nhanthien211 1
Releases(1.4.2)
  • 1.4.2(Jun 25, 2021)

    Released on: 2021-06-25

    CHANGELOG

    • #20 [FIX] Fixed controller's contentView autosizing when reusing a controller previously set as fixed-dimension controller
    • #15 [FIX] Fixed Demo Project
    Source code(tar.gz)
    Source code(zip)
  • 1.4.1(Oct 1, 2020)

    Released on 2020-10-01

    CHANGELOG

    • #14 [FIX] Cannot update the height of the ScrollStackRow's contentView because it does not receive scrollStackRowSizeForAxis() event when its layout di change
    Source code(tar.gz)
    Source code(zip)
  • 1.4.0(Sep 25, 2020)

    Released on 2020-09-25

    CHANGELOG

    • #13 [FIX] Fixed an issue with separator visibility which is wrongly updated when setRows/addRows is called. isSeparatorHidden of ScrollStackRow is now a property of the row itself which is maintained regardless the order of the row (when autoHideLastRowSeparator is set).
    • [NEW] Compatibility with Swift Package Manager (SPM) 5.3
    Source code(tar.gz)
    Source code(zip)
  • 1.3.3(Jun 5, 2020)

  • 1.3.1(Apr 21, 2020)

    Released on: 2020-04-21

    CHANGELOG

    • #11 [FIX] reloadContentFromStackView() should be called before getting the new size of the row (askForCutomizedSizeOfContentView())
    Source code(tar.gz)
    Source code(zip)
  • 1.3.0(Jan 26, 2020)

    Released on: 2019-01-26

    CHANGELOG

    • #10 [FIX]Support for lightweight rows layouts using plain UIView instances (without associated UIViewControllers).
      (This is especially useful when you don't have a complex logic in your views and you want to use ScrollStack to make custom layout and keep your code lightweight)
    Source code(tar.gz)
    Source code(zip)
  • 1.2.3(Jan 14, 2020)

    Released on: 2019-01-14

    CHANGELOG

    • #9 Removed unnecessary call to layoutIfNeeded() in rowPadding set property of ScrollStackRow in order to prevent multiple layout call which may causes AL errors.
    Source code(tar.gz)
    Source code(zip)
  • 1.2.2(Jan 14, 2020)

  • 1.2.1(Jan 12, 2020)

    Released on: 2019-01-12

    CHANGELOG

    • #7 contentSize is not correctly dispatched to scrollStackContentSizeDidChange() delegate function the first time you set content of the stack
    Source code(tar.gz)
    Source code(zip)
  • 1.2.0(Jan 12, 2020)

    Released on: 2019-01-12

    CHANGELOG

    • #5 Added rowPadding property to setup the insets of the contentView property of the ScrollStackRow
    • #6 Added two new methods in ScrollStackControllerDelegate protocol: scrollStackDidUpdateLayout() to observer layout changes and scrollStackContentSizeDidChange()to observe changes in contentSize of the stack view.
    Source code(tar.gz)
    Source code(zip)
  • 1.1.0(Dec 24, 2019)

  • 1.0.3(Dec 10, 2019)

    Released on: 2019-12-10

    CHANGELOG

    • #2 Fix for reloadContentFromStackView delegate function which is called even at first setup.
    • #3 Add setRows() to setup rows directly by removing the current.
    Source code(tar.gz)
    Source code(zip)
  • 1.0.2(Nov 4, 2019)

  • 1.0.1(Oct 16, 2019)

  • 1.0.0(Oct 16, 2019)

Owner
Daniele Margutti
iOS & macOS Engineer, UI/UX Lover. Tech Lead at Immobiliare.it. Author of www.offnotes.org. Continuous improvement is better than delayed perfection
Daniele Margutti
Full configurable spreadsheet view user interfaces for iOS applications. With this framework, you can easily create complex layouts like schedule, gantt chart or timetable as if you are using Excel.

kishikawakatsumi/SpreadsheetView has moved! It is being actively maintained at bannzai/SpreadsheetView. This fork was created when the project was mov

Kishikawa Katsumi 34 Sep 26, 2022
A set of UIKit helpers that simplify the usage of UIKit view's and controller's in SwiftUI.

A set of UIKit helpers that simplify the usage of UIKit view's and controller's in SwiftUI. Many of these helpers are useful even in a pure UIKit project.

SwiftUI+ 6 Oct 28, 2022
Neumorphism framework for UIKit.

NeumorphismKit is neumorphism framework for UIKit. Requirements iOS 12.0+ Swift 5.1+ Versions NeumorphismKit version Xcode version 1.0.0 Xcode 11+ 1.1

y-okudera 42 Dec 13, 2022
Easily use UIKit views in your SwiftUI applications. Create Xcode Previews for UIView elements

SwiftUIKitView Easily use UIKit views in SwiftUI. Convert UIView to SwiftUI View Create Xcode Previews from UIView elements SwiftUI functional updatin

Antoine van der Lee 682 Dec 29, 2022
Make ComposableArchitecture work with UIKit

ComposableUIKit The ComposableArchitecture (TCA) library provides a way of structuring Swift code with the Redux-pattern. It is highly optimized for S

Manuel Maly 11 Nov 5, 2022
Combine publisher bridges for UIKit

Combine publisher bridges for UIKit

Combine Community 1.3k Jan 1, 2023
Swift extensions for UIKit.framework.

XUIKit Example To run the example project, clone the repo, and run pod install from the Example directory first. Requirements Installation XUIKit is a

FITZ 0 Oct 22, 2021
Convenient domain specific language for writing programmatic UI built over UIKit and more.

XYKit Swifty and convenient domain specific language for creating programmatic UI in a more declarative way and more than that. Built on top of UIKit

Denis Goloborodko 1 Nov 5, 2021
🎸🎸🎸 Common categories for daily development. Such as UIKit, Foundation, QuartzCore, Accelerate, OpenCV and more.

?????? Common categories for daily development. Such as UIKit, Foundation, QuartzCore, Accelerate, OpenCV and more.

77。 423 Jan 4, 2023
Example Catalyst app that is shown in a UIKit popover underneath an NSStatusItem

CatalystStatusItemPopoverExample Example Catalyst app that is shown in a UIKit popover underneath an NSStatusItem. References How to use macOS Specifi

Tom Irving 9 Sep 8, 2022
Create descriptive UIKit screens, faster!

Columbina's DeclarativeUIKit Create descriptive UIKit screens, faster! Get rid of constraints manipulation and use declarative language to create your

Columbina 2 Dec 12, 2021
Application to test MVVM architecture with Combine and UIKit librarys.

Application to test MVVM architecture with Combine and UIKit librarys.

Jose Javier Escudero GΓ³mez 0 Dec 14, 2021
🎨 View instance initializing sugar for Swift & UIKit

?? View instance initializing sugar for Swift & UIKit

Yongjun Lee 11 Dec 1, 2022
xTensions is a collection of useful class extensions for UIKit.

xTensions Intro xTensions is a collection of useful class extensions for UIKit. Swift Package Manager Note: Instructions below are for using SwiftPM w

Alexandre Garrefa 0 Nov 28, 2021
A simple and elegant UIKit for iOS.

HamsterUIKit A simple and elegant UIKit(Chart) for iOS, written in Swift. ?? Curve and bar Charts. ?? Protocols are designed based on UIKit(UITableVie

Howard Wang 30 Oct 2, 2022
πŸ“ Declarative UIKit in 10 lines of code.

Withable ?? Declarative UIKit in 10 lines of code. See corresponding article at Declarative UIKit with 10 lines of code A simple extension instead of

Geri BorbΓ‘s 14 Dec 20, 2022
A child view controller framework that makes setting up your parent controllers as easy as pie.

Description Family is a child view controller framework that makes setting up your parent controllers as easy as pie. With a simple yet powerful publi

Christoffer Winterkvist 246 Dec 28, 2022
An easy to use FAQ view for iOS written in Swift

FAQView An easy to use FAQ view for iOS written in Swift. This view is a subclass of UIView. Setup with CocoaPods If you are using CocoaPods add this

Mukesh Thawani 467 Dec 5, 2022
Kit for building custom gauges + easy reproducible Apple's style ring gauges.

GaugeKit ##Kit for building custom gauges + easy reproducible Apple's style ring gauges. -> Example Usage Open GaugeKit.xcworkspace and change the sch

Petr Korolev 1k Dec 23, 2022