XCode: Is it possible to create reusable stack views? - ios

Is it possible to create reusable stack views on the story board that can be used dynamically to be generated at a later time? Sort of a template/widget/component.
I am aware that I can do this with a class but if I am able to visually generate a set of components that can be re-used at a latter time I may be able to let our designers make changes to storyboards directly.

Yes -- you can do this with any UIView. There are many tutorials for this (e.g. http://onedayitwillmake.com/blog/2013/07/ios-creating-reusable-uiviews-with-storyboard/)
The basic idea is to drag one onto Storyboard or XIB, make a custom class for it, then implement the view's awakeFromNib to load it.

Yes.It is.
Create a empty xib and then add a stack view to it.
Then create a class which extends UIStackView.
class stackView: UIStackView {
var contentView : UIStackView!
override init(frame: CGRect) {
super.init(frame: frame)
xibSetup()
}
required init(coder: NSCoder) {
super.init(coder: coder)
xibSetup() }
func xibSetup() {
contentView = loadViewFromNib()
contentView.frame = bounds
contentView.autoresizingMask = [UIViewAutoresizing.flexibleWidth, UIViewAutoresizing.flexibleHeight]
addSubview(contentView)
}
func loadViewFromNib() -> UIStackView! {
let view: UIStackView? = Bundle.main.loadNibNamed("stackView", owner: nil, options: nil)?.first as! UIStackView?
return view
}
Create a viewController.Add a stackView to it.In StackView properties, goto 3rd bar which named as custom class, for class name give stackView class name

Related

Confused about how/where to set a Xib's height

I have a custom view and xib. I use this custom view in one of my storyboard's controller views.
I have a use case where I want to be able to hide the custom view (and bring its height to zero). Right now, I set the height in the interface builder and set constraints to the superview's edges:
As you can see, I want its height to be 84 everywhere.
Now here is my custom view's class:
import UIKit
#IBDesignable
class BannerView: UIView {
#IBOutlet var contentView: UIView!
override init(frame: CGRect) {
super.init(frame: frame)
initialize()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
initialize()
}
override func awakeFromNib() {
super.awakeFromNib()
initialize()
}
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
initialize()
contentView?.prepareForInterfaceBuilder()
}
func initialize() {
guard let view = loadViewFromNib() else { return }
view.frame = self.bounds
view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
addSubview(view)
contentView = view
}
func loadViewFromNib() -> UIView? {
let bundle = Bundle(for: type(of: self))
let nib = UINib(nibName: "BannerView", bundle: bundle)
return nib.instantiate(withOwner: self, options: nil).first as? UIView
}
func hide() {
// Hide the view and set its height to zero here
}
}
But, now I'm confused... should I also be setting a height constraint on the custom view when I load it into one of my storyboards? Or should its height be 84 everywhere and I shouldn't have to specify it any further?
Also, how would I hide the custom view and set its height to zero in the above hide() function?
There are several ways to do this... here's one.
Give the content of your xib constraints to make its height 84-pts. You haven't shown your xib's layout, but I'll assume you know how to do that.
Then, when you add BannerView to your main view (I'm guessing that's what you're doing), embed it in a Vertical UIStackView with these properties:
Now, when you set bannerView.isHidden = true, the stack view automatically removes it from the height calculations, resulting in the stack view having a height of Zero.
Setting bannerView.isHidden = false will then re-display the banner view along with its height.
As you want the view's height to be 84 everywhere I think you should add a height constraint outlet and set the value 84.
Set the constraint value to 0 to hide the view (I highly suggest not to hide some view by doing this).

clipsToBounds not working when scaling child views

I have a custom xib that has a horizontal stack view with 3 child views. I use this view in other xibs. When the user clicks on one of these child views, I scale it using:
view.transform = CGAffineTransform(scaleX: 2, y: 2)
When the view is scaled, it's actually meant to appear out of the parent view's bounds. However, when I click the child view, it scales it fine, but the view is cut off.
I've double checked that all of my views in the xib have clipsToBounds set to false, but it's still cutting it off.
Here is what my xib class looks like:
class CustomView: UIView {
#IBOutlet var contentView: UIView!
#IBOutlet weak var stackView: UIStackView!
override init(frame: CGRect) {
super.init(frame: frame)
initialize()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
initialize()
}
func initialize() {
Bundle.main.loadNibNamed("CustomView", owner: self, options: nil)
addSubview(contentView)
contentView.frame = self.bounds
contentView.autoresizingMask = [.flexibleHeight, .flexibleWidth]
}
... some other things that I've left out that aren't important ...
}
Is there something I'm missing? How do I make the child views not get clipped/cut off?
What about the view hosting the XIB? (CustomView in this case). Set clipsToBounds=false in your initialize. Otherwise, take a look at Xcode's view hierarchy debugger and see if there's another view that causing the clipping.

Adding a Table View Cell to a Table View within a View

I have a subclass of UIView (MyView) that I've hooked up to a NIB file.
class MyView: UIView {
#IBOutlet var contentView: UIView!
#IBOutlet weak var tableView: UITableView!
override init(frame: CGRect) {
super.init(frame: frame)
self.setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.setup()
}
let nibName = "MyView"
private func setup() {
let bundle = Bundle.init(for: type(of: self))
bundle.loadNibNamed(nibName, owner: self, options: nil)
contentView.frame = self.bounds
contentView.autoresizingMask = [.flexibleHeight, .flexibleWidth]
self.addSubview(contentView)
}
}
This view is simple; containing only one UITableView (or tableView). However, I want to add a UITableViewCell with a UILabel to tableView but the storyboard is not letting me do this.
I understand that MyView is not a view controller and therefore should not (if following an MVC pattern) implement the various table view data source / delate methods, but, still, why can I not add this table view cell to the table view within this custom view?
My aim was to then have some UIViewController subclass that has an instance of MyView, i.e.
var myView = MyView(),
which it then controls the datasource and delegate methods for, i.e.
myView.tableView.dataSource = self.
Finally, I've attached a screenshot showing that I am unable to add this table view cell to the table view.
You cannot add prototype cells to table views in xib files - only in storyboards.
You can create your table view in one xib, and your table view cell in another xib, if you want.
Or, you can create a second storyboard that would contain a table view and it would support cell prototypes.

UITableViewCell with a common element

I have a scenario where in I need to create around 10 different prototypes of UITableViewCells. How ever all of these cells have some UI Elements (area marked in black) in common. And there is an area (marked in yellow) which is different for all these prototypes.
Is there a way I can abstract all the common UI Elements like the way it is done for contentView in UITableViewCell?
I tried to create a TableViewCell with all these elements and empty UIView to hold the customizations and planned to programatically load UIView (created in separate xib) into it.
The problem is I am not able to load the UIView into the UITableViewCell without loosing the constraints?
How to load a custom view with constraints into another?
Or Is there a way to create a custom UITableViewCell like the one in IB?
Create LoadableFromXibView subclass
Create a xib file, set the File's owner as your subclass
Drag outlets and design your view as usual
In your cell insert a subview that will be your reusable part of the cell and set its name as the new LoadableFromXibView subclass that you have created.
Source here: https://gist.github.com/DenHeadless/c3d682e7f499113109d6
class LoadableFromXibView: UIView {
var view = UIView()
override init(frame: CGRect) {
super.init(frame: frame)
xibSetup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
xibSetup()
}
func xibSetup() {
view = loadViewFromXib()
view.frame = bounds
view.autoresizingMask = [.FlexibleWidth, .FlexibleHeight]
backgroundColor = .clearColor()
addSubview(view)
}
private func loadViewFromXib() -> UIView {
let bundle = NSBundle(forClass: self.dynamicType)
let nib = UINib(nibName: String(self.dynamicType), bundle: bundle)
let view = nib.instantiateWithOwner(self, options: nil).first as! UIView
return view
}
}

Making a custom UIView subview that fills its superview

So I wrote my own custom view with its own initializer. However, when my main view loads my custom view gets depicted in a wrong way. It takes bounds as 600x600 rectangle, while the superview is 375x607. I did try to put auto constraint, seems not to work. I tried to do it programmatically in the subview initialization, but whenever I try to initialize it's bounds property to its superview bounds I get nil in superview.
override init(frame: CGRect) {
super.init(frame: frame)
self.setup()
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.setup()
}
func setup() {
initPathLayer()
initHandleView()
initHandlePanGestureRecognizer()
layoutPathLayer()
layoutHandleViews()
}
I tried everything there is on the internet to make subview fill its superview, but I think that subview gets initialized before superview? Is that possible ? In the ViewController a have my custom view declared as an Outlet connection. I'm sure that the problem should be super easy and it's me who doesn't know the way Swift initializes the view.
Any ideas ?
Thank You.
Firstly, a Views init method is not the best place to perform a layout. The view could be resized at a later point which is typically always the case if the view is loaded from a Xib or you are creating it in a View Controllers viewDidLoad function.
That being said you have several approaches:
1. Use Auto Layout
Interface Builder
This can be done either in Interface Builder or programmatically. In Interface Builder you simply use the 'Pin' option and select all the sides of the view
When this is done you should be able to see your constraints in the Size inspector looking as follows:
Programmatically
Alternatively you can always add your constraints programmatically in your initializer:
override init(frame: CGRect) {
let view = UIView(frame: .zero)
view.translatesAutoresizingMaskIntoConstraints = false
super.init(frame: frame)
let viewsDict = ["view": view]
addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-0-[view]-0-|", options: .allZeros, metrics: nil, views: viewsDict))
addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|-0-[view]-0-|", options: .allZeros, metrics: nil, views: viewsDict))
addSubview(view)
}
convenience required init(coder aDecoder: NSCoder) {
self.init(frame: .zero)
}
2. Manual Layout
Resizing Masks
For this you can either use resizing masks or you can control the frame in layoutSubviews. Resizing masks tell the system how the view should be sized relative to the superview:
override init(frame: CGRect) {
let view = UIView(frame: CGRectZero)
view.translatesAutoresizingMaskIntoConstraints = false
super.init(frame: frame)
view.frame = bounds
view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
addSubview(view)
}
Layout Subviews
Lastly, you can override layoutSubviews and go from there:
override func layoutSubviews() {
super.layoutSubviews()
view.frame = bounds
}

Resources