A controller that uses a UIStackView and view controller composition to display content in a list

Overview

StackViewController Carthage compatible CocoaPods Compatible

Overview

StackViewController is a Swift framework that simplifies the process of building forms and other static content using UIStackView. For example, the form below is implemented using a StackViewController:

StackViewController Example App

Design Rationale

The purpose of this project is two-fold: encouraging design patterns that are more suitable for building content like the form pictured above, and providing tools to make the process simpler. The following sections contain a summary of the existing solutions and how we can improve upon them.

Building Forms with UITableView (Is Difficult)

Traditionally, iOS developers have utilized UITableView to build forms and other relatively static list-based user interfaces, despite the UITableView API being a poor fit for such tasks. UITableView is designed primarily for dynamic content, and a lot of the functionality that it provides is only necessary for dynamic content. Using it to build static user interfaces results in a lot of boilerplate code in implementing many data source and delegate methods.

Another major issue is the difficulty of implementing variable-height content with UITableView. When building a form, for example, a common need is the ability to display a field (e.g. a text view) whose dimensions automatically change as the content inside it changes. One half of the problem is knowing how to size the cell — this is typically done by either manually computing the size in -tableView:heightForRowAtIndexPath: or by using Autolayout, estimated row heights, and the self-sizing table view cell feature introduced in iOS 8. The other half of the problem is notifying the table view that it should update the layout for a cell once the content inside the cell changes. This can involve ugly hacks like calling -[UITableView beginUpdates] and -[UITableView endUpdates] to force relayout.

The bottom line is that UITableView is the wrong tool for the job.

Introducing UIStackView

UIStackView, introduced in iOS 9, provides a clean abstraction over Autolayout for laying out a horizontal or vertical stack of views. By composing multiple instances of UIStackView, the vast majority of common user interface layouts can be built quite easily without the need to manually create and remove layout constraints.

UIStackView is well suited for the task of building forms and other static content, but it has some shortcomings when applied to that particular use case. There are things that UITableView and UITableViewController provide that we often take for granted: scrolling support, cell separators, and other conveniences. UIStackView doesn't have this functionality built in, so one of the goals of this library is to fill in those key pieces of missing functionality to the point where using a stack view is easier than using a table view for the same task.

View Controllers over Views

A strong indicator of poorly designed iOS code is a bad separation of responsibilities between the view and the view controller, in accordance with the MVC (Model-View-Controller) pattern. The Massive View Controller anti-pattern is a common occurrence where the view controller simply does too much, absorbing responsibilities from the model and view layers. Conversely, there is also an anti-pattern where the view takes on many controller-like responsibilities rather than just focusing on the layout and rendering of the content.

StackViewController defines a single API for using both UIView and UIViewController instances to provide content. UIView instances can be used when the content being displayed is simple and non-interactive (e.g. a static label). UIViewController instances can be used for more complex controls where there needs to be a controller in addition to the view, when, for example, a view displays a visual representation of state from a model that needs to be updated as the user interacts with the view.

View Controller Composition

Composition over inheritance is a fundamental principle of object-oriented programming.

This principle has always been used in iOS view hierarchies, where more complex views are composed out of simpler ones (e.g. how a UIButton contains a UILabel and a UIImageView that render its content). However, there was no "official" way to compose view controllers until the introduction of view controller containment in iOS 5. It was possible to mimic behaviour like this prior to iOS 5, but handling the propagation of events between parent and child view controllers and transitions between child view controllers was difficult to get right, which are all problems that the view controller containment API solves.

In the same way that you can create complex layouts by composing multiple UIStackView instances, you can use the view controller containment API to compose multiple instances of StackViewController to create a hierarchy of view controllers where each content view is backed by a corresponding view controller that cleanly separates the responsibilities, instead of handling all of that at the view level (an anti-pattern, as mentioned earlier).

Features

The framework provides two primary classes: StackViewContainer and StackViewController. StackViewContainer wraps a UIStackView and implements the following additional features:

  • Scrolling support by embedding the UIStackView inside a UIScrollView with automatic management of associated constraints
  • Autoscroll behaviour to automatically adjust layout and scroll to the view being edited when the keyboard appears (the same behaviour implemented by UITableViewController)
  • Customizable separator views between content views that can be toggled on a per-view basis and are managed automatically when content views are inserted and removed
  • Other minor conveniences like support for background views and changing the background color (since UIStackView doesn't draw a background)

StackViewController is a subclass of UIViewController that uses an instance of StackViewContainer as its view, and adds support for adding content using view controller containment (i.e. view controller composition). This means that you can use view controllers and/or views to represent your content instead of just views, and StackViewController automatically handles adding and removing them as child view controllers.

Example

The included example app, pictured above, demonstrates the usage of both StackViewContainer on its own (the image attachment control) as well as StackViewController (the full form).

License

This project is licensed under the MIT license. See LICENSE.md for more details.

Comments
  • Limit source file types to Swift and headers

    Limit source file types to Swift and headers

    This fixes a bug where the Info.plist file from this repo was included in the compile sources phase, which causes a build failure with the new Xcode 9 build system.

    opened by mergesort 6
  • Seeting a Background Image for StackViewController

    Seeting a Background Image for StackViewController

    Hi All, Im using StackViewController in one of my Projects. Currently im using "backgroundcolor" property to set the BG color of the View. Now i have a new Req in which i need to set an Image as the Background. Is it Possible using this Framework ? I tried adding a subview to the "backgroundView" property and it is not working out. Please provide some pointers.

    Thank you :)

    opened by jenixgnanadhas 4
  • Deprecate internal properties

    Deprecate internal properties

    Currently we expose properties for backgroundColor, backgroundView, stackView, axis seperatorViewFactory and scrollView. Ideally a user won't be able to modify the state of stackView, seperatorViewFactory and scrollView without going through StackViewController directly.

    Being able to modify properties of the stack view directly makes it possible for a user to reconfigure it in such a way that it isn't compatible with the current configuration of the StackViewController anymore. Similarly being able to change the axis after initialization complicates the implementation of StackViewController because it always needs to account for the fact the the axis might change.

    axis, separatorViewFactory and backgroundView should be injected in the initializer and not changed after StackViewConroller is initialized.

    stackView scrollView properties that we want the user to modify should be exposed directly on the StackViewController. For example if we want the user to be able to scroll we could expose scroll(toViewController:) or scroll(toRect:) on StackViewController directly.

    We can keep backgroundColor, but I think it should be implemented as a convenience for backgroundView.backgroundColor =.

    opened by klaaspieter 3
  • Podspec / Release out of date

    Podspec / Release out of date

    Hi there. Love this project and the quality of the code! Can you do us a favor and release a new version with the latest changes? The current podspec and release tags cause an older version of the code to be installed (pre Swift 3) Thanks!

    opened by traylewin 2
  • Auto expand rows

    Auto expand rows

    When running the app on iPhone 6, after adding the third picture, it should be on a line below, instead of scrolling off to the side. The same way the text entry field expands downwards, the container of the cells should also expand accordingly. (BTW, fantastic work done on the StackViewController)

    opened by GJNilsen 2
  • Initial README draft

    Initial README draft

    This is my initial draft of the README, containing the overview of the project and the detailed design rationale. Feel free to nitpick, I want to make sure this gets the idea across properly.

    opened by indragiek 2
  • Fixes index error when replacing items array via assignment

    Fixes index error when replacing items array via assignment

    This bit of code had an error:

    /// The items displayed by this controller
    open var items: [StackViewItem] {
        get { return _items }
        set(newItems) {
            for (index, _) in _items.enumerated() { // <-- HERE
                removeItemAtIndex(index)
            }
            for item in newItems {
                addItem(item, canShowSeparator: true)
            }
        }
    }
    

    As it looks through the array with the index increasing it will hit an Index Not Found exception once the number of remaining items is less than the current index.

    Simple fix was to reverse the enumeration.

    /// The items displayed by this controller
    open var items: [StackViewItem] {
        get { return _items }
        set(newItems) {
            for (index, _) in _items.enumerated().reversed() { // <-- SIMPLE FIX
                removeItemAtIndex(index)
            }
            for item in newItems {
                addItem(item, canShowSeparator: true)
            }
        }
    }
    
    opened by sgtsquiggs 1
  • WKWebView as StackViewController item

    WKWebView as StackViewController item

    Hi, I am currently considering to use this good library in my project to show user inbox email. So, in the Demo App, I'm trying to add a WKWebView as StackViewController item in place of UITextView but web view is not shown. This's modified setupStackViewController method:

    import WKWebKit
    
    ....
    
    fileprivate func setupStackViewController() {
         let toFieldController = LabeledTextFieldController(labelText: "To:")
         firstField = toFieldController.view
         stackViewController.addItem(toFieldController)
         stackViewController.addItem(LabeledTextFieldController(labelText: "Subject:"))
            
         let webView = WKWebView()
         webView.loadHTMLString("This field automatically expands as you type, no additional logic required", baseURL: nil)
         webView.scrollView.isScrollEnabled = false
         stackViewController.addItem(webView, canShowSeparator: false)
            
         stackViewController.addItem(ImageAttachmentViewController())
     }
    

    Debug View Hierarchy tells me Scrollable content size is ambiguous. What I'm doing wrong?

    Thanks for your help, Giorgio

    opened by giofid 1
  • removeContentViewAtIndex doesn't actually remove the UIView from the superview

    removeContentViewAtIndex doesn't actually remove the UIView from the superview

    stackView.removeArrangedSubview does not actually remove the subview from its superview. This leaves a big pile of UIViews sitting around on screen afterwards.

    This will fix it: open func removeContentViewAtIndex(_ index: Int) { precondition(index >= items.startIndex) precondition(index < items.endIndex)

        let item = items[index]
        if items.count >= 1 && index == (items.endIndex - 1) && index > 0 {
            let previousItem = items[(index - 1)]
            if let separatorView = previousItem.separatorView {
                stackView.removeArrangedSubview(separatorView)
                separatorView.removeFromSuperview()
                previousItem.separatorView = nil
            }
        }
        stackView.removeArrangedSubview(item.contentView)
        item.contentView.removeFromSuperview()
        if let separatorView = item.separatorView {
            stackView.removeArrangedSubview(separatorView)
            separatorView.removeFromSuperview()
        }
        items.remove(at: index)
        _contentViews.remove(at: index)
    }
    
    opened by tankadesign 1
  • Fix handling of StackViewContainer.backgroundColor

    Fix handling of StackViewContainer.backgroundColor

    Setting StackViewContainer.backgroundColor while there is also a backgroundView currently does not have the intended behaviour because the backgroundView goes underneath the stackView, which does not draw its own background color. The stackView is now wrapped in a container view that draws a background to prevent this issue.

    opened by indragiek 1
  • Fix usage of axis parameter in SeparatorView

    Fix usage of axis parameter in SeparatorView

    Previously, the SeparatorView's actual axis would be perpendicular to the axis of the stack view, which was passed into this initializer. This (largely unintentional) convention was convenient because you could use the stack view's axis directly as the parameter to the SeparatorView initializer, but this made no sense when using the SeparatorView as a standalone view.

    This pull request changes that convention to the opposite (the axis parameter passed into init is the actual axis of the separator) and adds a convenience method for creating separator view factories because it's no longer a trivial one liner.

    opened by indragiek 1
  • StackView needs custom activateSuperviewHuggingConstraints when using custom stack view spacing

    StackView needs custom activateSuperviewHuggingConstraints when using custom stack view spacing

    Perhaps there is another intended way to do this, but if I set the .stackView.spacing property, the top of the stack view is still flush with the AutoScrollView instead of offset by the spacing amount which makes the layout look wrong. It looks like simply passing the stack view spacing property as a edge inset to activateSuperviewHuggingConstraints in commonInit() on line 130 of StackViewContainer might do the trick

    opened by aharriscrowne 0
  • How to access to subject: and to: fields text?

    How to access to subject: and to: fields text?

    Hi! Thank you for great plugin! Could you please explain, how to easily access to subject: and to: fields text to be able send message? Now only bodyText field is accessible thru ViewController. Thanks.

    opened by achirkof 0
Releases(0.6.0)
Owner
Seed
Seed - Mobile Business Banking
Seed
A micro UIStackView convenience API inspired by SwiftUI

Stacks A micro UIStackView convenience API inspired by SwiftUI. let stack: UIView = .hStack(alignment: .center, margins: .all(16), [ .vStack(spaci

Alexander Grebenyuk 74 Jul 27, 2022
Porting UIStackView to iOS 7+

OAStackView iOS 9 introduced the very cool UIStackView, UIStackView can be used to easily create simple and complex layouts. As expected UIStackView c

Omar Abdelhafith 2.2k Dec 3, 2022
An alternative to UIStackView for common Auto Layout patterns.

StackLayout StackLayout builds on Auto Layout to make some of the most common layouts easier to manage. It creates the constraints that you need and a

Bridger Maxwell 76 Jun 29, 2022
List tree data souce to display hierachical data structures in lists-like way. It's UI agnostic, just like view-model and doesn't depend on UI framework

SwiftListTreeDataSource List tree data souce to display hierachical data structures in lists-like way. It's UI agnostic, just like view-model, so can

Dzmitry Antonenka 26 Nov 26, 2022
A custom stretchable header view for UIScrollView or any its subclasses with UIActivityIndicatorView and iPhone X safe area support for content reloading. Built for iOS 10 and later.

Arale A custom stretchable header view for UIScrollView or any its subclasses with UIActivityIndicatorView support for reloading your content. Built f

Putra Z. 43 Feb 4, 2022
SwiftUI view enabling navigation between pages of content, imitating the behaviour of UIPageViewController for iOS and watchOS

PageView SwiftUI view enabling page-based navigation, imitating the behaviour of UIPageViewController in iOS. Why SwiftUI doesn't have any kind of pag

Kacper Rączy 365 Dec 29, 2022
A SwiftUI Library for creating resizable partitions for View Content.

Partition Kit Recently Featured In Top 10 Trending Android and iOS Libraries in October and in 5 iOS libraries to enhance your app! What is PartitionK

Kieran Brown 230 Oct 27, 2022
A SwiftUI ScrollView that only scrolls if the content doesn't fit in the View

ScrollViewIfNeeded A SwiftUI ScrollView that only scrolls if the content doesn't fit in the View Installation Requirements iOS 13+ Swift Package Manag

Daniel Klöck 19 Dec 28, 2022
A nice iOS View Capture Swift Library which can capture all content.

SwViewCapture A nice iOS View Capture Library which can capture all content. SwViewCapture could convert all content of UIWebView to a UIImage. 一个用起来还

Xing Chen 597 Nov 22, 2022
Reel Search is a Swift UI controller that allows you to choose options from a list

REEL SEARCH Reel Search is a Swift UI controller that allows you to choose options from a list We specialize in the designing and coding of custom UI

Ramotion 2.5k Dec 21, 2022
Simple and highly customizable iOS tag list view, in Swift.

TagListView Simple and highly customizable iOS tag list view, in Swift. Supports Storyboard, Auto Layout, and @IBDesignable. Usage The most convenient

Ela Workshop 2.5k Jan 5, 2023
Advanced List View for SwiftUI with pagination & different states

AdvancedList This package provides a wrapper view around the SwiftUI List view which adds pagination (through my ListPagination package) and an empty,

Chris 246 Jan 3, 2023
App that uses iTunes Search API with SwiftUI for iOS 15+

ItunesSearchApp App that uses iTunes Search API with SwiftUI for iOS 15+ Documentation iTunes Search API: https://developer.apple.com/library/archive/

Karin Prater 22 Dec 25, 2022
Placeholder views based on content, loading, error or empty states

StatefulViewController A protocol to enable UIViewControllers or UIViews to present placeholder views based on content, loading, error or empty states

Alexander Schuch 2.1k Dec 8, 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
Fetch the star wars api from all the planets and list and show details using Swift UI and Combine

Star Wars Planets Fetch the star wars planet data by using stat war api, list and show details using SwiftUI and Combine frameworks ?? Swift UI Framew

null 1 Aug 10, 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
A library, which adds the ability to hide navigation bar when view controller is pushed via hidesNavigationBarWhenPushed flag

HidesNavigationBarWhenPushed A library, which adds the ability to hide navigation bar when view controller is pushed via hidesNavigationBarWhenPushed

Danil Gontovnik 55 Oct 19, 2022