To create an iOS app, you have to deal with ALOT of views. So, managing different views will become painful as the project grows. Sometimes, a single view will be used in multiple places with small variations. You feel that if there's a way to manage every view in one place would save you alot of trouble. Well, this is what MSAutoView does exactly.
MSAutoView is available through CocoaPods. To install it, simply add the following line to your Podfile:
pod 'MSAutoView'
- XCode 9
This is an example of how your view files will be structured when using MSAutoView:
Each view will have its own xib file and swift file. The xib file will contain the view hierarchy, and the swift file will hold the logic for this view.
MSAutoView
is a subclass of UIView
. When creating a class that inherits from MSAutoView
, it automatically finds the corresponding xib and adds it as a subview. It also creates top, bottom, left and right constraints for the subview to hold it in place.
Note: The view in the xib should not have constraint ambiguity or it would not show properly
-
Create a xib file and add the reusable view to it (Example):
-
Create a swift file and add a class that inherits from
MSAutoView
import UIKit
import MSAutoView
class ListingView: MSAutoView {
}
Note: For the minimal configuration to work, the class's name should be the same as the xib's name
-
In the storyboard, add a normal view to your view controller and set its class to the one created (
ListingView
) -
Run the project, the view should contain the content of the xib
Ofcourse, the reusable view will be useless if you can't pass data to it programmatically. To do that, follow the steps below:
-
Set the xib
File's Owner
class to the class created previously -
Add outlets to the class:
class ListingView: MSAutoView {
@IBOutlet weak var titleLabel: UILabel!
@IBOutlet weak var detailsLabel: UILabel!
@IBOutlet weak var priceLabel: UILabel!
}
-
In the xib file, connect the outlets to their respective views
-
In the view controller, create an outlet for the view
@IBOutlet weak var listingView: ListingView!
-
Connect the view controller's outlet to the view in the storyboard
-
Change the text values of the labels in the view controller:
listingView.titleLabel.text = "This is a default title"
listingView.detailsLabel.text = "This is a default details"
listingView.priceLabel.text = "300"
It's not a good convention to directly update the text in the labels. So, you can create variables to hold the values, and update the view when you change the values. To do that, follow the steps below:
- In the
ListingView
class file, add the following variables:
class ListingView: MSAutoView {
//Outlets
var title: String?
var details: String?
var price: String?
}
- Override
updateView()
function to set the label texts:
class ListingView: MSAutoView {
//Outlets
//Variables
override func updateView() {
super.updateView()
titleLabel.text = title
detailsLabel.text = details
priceLabel.text = price
}
}
- Set the values anywhere in the view controller and update the view:
listingView.title = "This is a default title"
listingView.details = "This is a default details"
listingView.price = "300"
listingView.updateView()
You can use inspectable variables to hold the values for the labels:
class ListingView: MSAutoView {
//Outlets
@IBInspectable var title: String?
@IBInspectable var details: String?
@IBInspectable var price: Double = 0
//Functions
}
After creating the inspectable variables, you can set them either in the xib or the view controller
If you want to save the hassle of creating a new cell class for each view that is used in a UITableView
, you can use the generic class MSTableViewCell<T>
supplied by the repository. You can use it as follows:
- Create a table view
- Register the cell programmatically:
tableView.register(MSTableViewCell<ListingView>.self, forCellWithReuseIdentifier: "Cell")
Alternatively, MSAutoView
adds an extension to UIView
which has variables that return a table view cell from any view. So, you can register the cell as such:
tableView.register(ListingView.tableViewCell.self, forCellWithReuseIdentifier: "Cell")
- In the
tableView(_:cellForRowAt:)
, dequeue the cell with the reuse identifier like this:
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") as! MSTableViewCell<ListingView>
- You can access the encapsulated view using the vairable
mainView
on the cell:
cell.mainView.doSomething()
This class is an open
class so you can subclass it as you wish to add more features.
Creating a collection view cell from any view acts similar as creating a table view cell. But, you would use the extension variable collectionViewCell
instead of the tableViewCell
The MSAutoView
now has two properties which can be set to be able to retrieve the table view cell/collection view cell later on from the view
public weak var tableViewCell: UITableViewCell?
public weak var collectionViewCell: UICollectionViewCell?
It works similar to the superView
variable, just set the cell when setting up the view, and in the delegate functions you can retrieve the corresponding cell by calling either view.tableViewCell
or view.collectionViewCell
Creating a scroll view is the same as creating a table view/collection view. Assuming that you have a tall view of class TallView
which has a label called anyLabel
, you can do the following in your view controller
let tallView = TallView()
tallView.anyLabel.text = "This is a dummy text"
view.addSubviewWithConstraints(tallView.scrollView)
You can do this in 2 ways:
- Set the value in code:
var title: String? = "Default Title"
- If it's an inspectable variable, set the value in the xib file
If your class variables are inspectables, you can change the default values in the storyboard
As a recap, the class will embed the xib's view in the main view by adding top, left, bottom and right constraints. To add padding to the constraints, you can set their constant value other than 0. You can do that in the initView()
function:
override func initView() {
super.initView { (top, left, bottom, right) in
top.constant = 10
left.constant = 10
bottom.constant = -10
right.constant = -10
}
updateView()
}
Note that the bottom and right constants should be negative to work as intended
Alternatively, you can add paddings later on:
fileprivate var leftLayoutConstraint: NSLayoutConstraint?
fileprivate var rightLayoutConstraint: NSLayoutConstraint?
override func initView() {
super.initView {[weak self] (top, left, bottom, right) in
self?.leftLayoutConstraint = left
self?.rightLayoutConstraint = right
}
}
// update the margin whenever you want.
func updateMargin(_ left: CGFloat, _ right: CGFloat) {
self.leftLayoutConstraint?.constant = left
self.rightLayoutConstraint?.constant = right
}
If you wish to name your xib something other than the class name, you can do the following:
- In the class file, override the
initView()
function:
class ListingView: MSAutoView {
//Outlets
//Variables
override func initView() {
self.xibName = "ListingView2"
super.initView()
}
}
class ListingView: MSAutoView {
//Outlets
//Variables
override func initView() {
self.xibBundle = Bundle(identifier: "Identifier")
super.initView()
}
}
If you inherit from another view and subclassing is not an option, there's a protocol that can be used to easily embed views:
public protocol MSXibEmbedding: AnyObject {
var xibBundle: Bundle? { set get }
var xibName: String? { set get }
func loadXibMainView(constraintsConfiguration: ConstraintsConfiguration?)
func loadXibItems(xibItemsConfiguration: XibItemsConfiguration?)
}
There's extension functions for the protocol functions so you don't have to worry about the actual implementation. Just make your view conform to the MSXibEmbedding
protocol and call loadXibMainView()
when you initialize your view
You can easily aggregate views by placing them inside each other. You can do that using the UIView
extension function below:
public func addSubviewWithConstraints(_ subview: UIView, constraintsConfiguration: ConstraintsConfiguration? = nil)
Example:
parentView.addSubviewWithConstraints(childView)
You might want to use a xib which has multiple top level views. Maybe there are also objects other than views. To do that, override the initView()
as follows:
override func initView() {
loadXibItems { (items) in
//Use xib items here, maybe save them in variables ...
}
}
Note: If you call loadXibItems
more than once, the previously loaded xib items will be discarded and the new items will be available. So you might get a nil
if you try to access a discarded item.
- Maher Santina - Initial work
This project is licensed under the MIT License - see the LICENSE.md file for details