Swift: Adding Taps to Custom Views - ios

I would like to add taps to views which subclasses another view, but my current approach does not get invoked, ie the seatNumber does not get printed when my seats are tapped as coded in the handleTap function. I have adapted my approach from the following this post and this. My code thus far:
//My viewController
class ViewController: UIViewController, UIScrollViewDelegate {
let scrollView = UIScrollView()
let cinema: CinemaView = {
let v = CinemaView()
return v
}()
override func viewDidLoad() {
super.viewDidLoad()
let seat1 = SeatView()
seat1.isVacant = false
seat1.seatNumber = "2A"
let seat2 = SeatView()
seat2.isVacant = true
seat2.seatNumber = "3B"
cinema.seats = [seat1, seat2]
let tap = UITapGestureRecognizer(target: seat1, action: #selector(handleTap))
seat1.addGestureRecognizer(tap)
seat1.isUserInteractionEnabled = true
view.addSubview(scrollView)
scrollView.addSubview(cinema)
}
#objc func handleTap(_ seat: SeatView) {
if let seatNumber = seat.seatNumber {
print("\(seatNumber) is tapped")
} else {
print("Seat tapped")
}
}
//My custom cinemaView
class CinemaView: UIView {
var seats = [SeatView]() {
didSet {
for v in self.subviews {
v.removeFromSuperview()
}
var seatRect = CGRect(x: 0, y: 0, width: 20, height: 20)
for seat in seats {
self.addSubview(seat)
seat.frame = seatRect
seatRect.origin.x += seatRect.size.width + 8
}
}
}
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
//My Custom seatView
class SeatView: UIView {
var isVacant: Bool = true
var seatNumber: String?
override init(frame: CGRect) {
super.init(frame: frame)
}
}
Note: some boilerplate codes have been omitted above to keep the code concise for this question.
My preferred approach is to add these taps inside the VC so that I can pass this info into another VC and push up to Firebase. Any advice would be much appreciated
EDIT: Added screenshot of UI

With the help of 3stud1ant3, I managed to figure out where went wrong.
After observing the view layout, the cinemaView was never added to the view even though the seatViews were added. By defining more constraints to cinemaView in viewDidLoad at the viewController and incorporating 3stud1ant3's code in the below answer post, the tap functions worked.
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .blue
cinema.seats = [seatView1, seatView2]
view.addSubview(scrollView)
scrollView.addSubview(cinema)
let views = ["scrollView": scrollView, "v": cinema] as [String : Any]
let screenHeight = view.frame.height
view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|[scrollView(\(screenHeight / 2))]", options: NSLayoutFormatOptions(), metrics: nil, views: views))
view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[scrollView]|", options: NSLayoutFormatOptions(), metrics: nil, views: views))
view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|-60-[v(50)]", options: NSLayoutFormatOptions(), metrics: nil, views: views))
//Previously, the below constraints were not added. Once these are added, the tap function works
cinema.leftAnchor.constraint(equalTo: scrollView.leftAnchor).isActive = true
cinema.topAnchor.constraint(equalTo: scrollView.topAnchor).isActive = true
cinema.widthAnchor.constraint(equalTo: scrollView.widthAnchor ,constant: 100).isActive = true
}

Related

Update NSLayoutConstraint animation issue

It's a simple test, all codes as below, why lblA has no animation when width changed to 30? but it has animation when width changed to 300.
import UIKit
class ViewController: UIViewController {
let lblA = UILabel()
var lblAWidthConstraint: NSLayoutConstraint?
var tag = false
// let lblB = UILabel()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.lblA.backgroundColor = .red
self.view.addSubview(self.lblA)
self.lblA.translatesAutoresizingMaskIntoConstraints = false
self.view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "|[v]"
, options: []
, metrics: nil
, views: ["v" : self.lblA]))
self.view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|[v]|"
, options: []
, metrics: nil
, views: ["v" : self.lblA]))
let widthConstraint = self.lblA.widthAnchor.constraint(equalToConstant: 30)
widthConstraint.isActive = true
self.lblAWidthConstraint = widthConstraint
//
let tap = UITapGestureRecognizer(target: self, action: #selector(tapView))
self.view.addGestureRecognizer(tap)
}
#objc func tapView() {
tag = !tag
self.lblAWidthConstraint?.constant = tag ? 300 : 30
UIView.animate(withDuration: 0.3) {
self.view.layoutIfNeeded()
}
}
}
[UPDATE] Finally I found this: Animating Frame of UILabel smoothly

Button on UIView does not respond unless I specify the correct CGRect

I am trying to implement a simple UIView that will show at the bottom of the screen when internet connection is lost and will have a dismiss button. My UIView class looks something like this.
import UIKit
class ConnectionLostAlertUIView: UIView {
#IBOutlet var view: UIView!
var viewConstraints: [NSLayoutConstraint] = []
override init(frame: CGRect) {
super.init(frame: frame)
Bundle.main.loadNibNamed("ConnectionLostAlertView", owner: self, options: nil)
self.addSubview(self.view)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
Bundle.main.loadNibNamed("ConnectionLostAlertView", owner: self, options: nil)
self.addSubview(self.view)
}
override func didMoveToSuperview() {
view.translatesAutoresizingMaskIntoConstraints = false
let bottomConstraint = view.bottomAnchor.constraint(equalTo: self.superview!.bottomAnchor)
let leftConstraint = view.leadingAnchor.constraint(equalTo: self.superview!.leadingAnchor)
let rightConstraint = view.trailingAnchor.constraint(equalTo: self.superview!.trailingAnchor)
viewConstraints = [bottomConstraint, leftConstraint, rightConstraint]
NSLayoutConstraint.activate(viewConstraints)
}
#IBAction func dismissButton(_ sender: Any) {
Logger.log(message: "Dismiss button was pressed", event: .d)
self.isHidden = true
}
And I add this UIView to my UIViewController by doing the following
var ConnectionLostAlert: ConnectionLostAlertUIView!
ConnectionLostAlert = ConnectionLostAlertUIView(frame: CGRect.zero)
self.view.addSubview(ConnectionLostAlert)
Well, I can use actual values for CGRect instead of zero but the problem is that after the constraints are added in my UIView, the button in my UIView stops responding. It works just fine if I actually set some CGRect coordinates and not use constraints in the UIView. Is there a better way to do what I am trying to achieve that will still allow me to handle the button event inside the UIView?
The problem is you are adding constraints from the inner view i.e view to the superview skipping the main view (ConnectionLostAlertUIView) that is actually being added to the super view.
What you should do instead is to add constraints between view and self first. Then when self(ConnectionLostAlertUIView) is added to the superview, add the constraints with super view then.
Do something like this:
#IBOutlet var view: UIView!
var viewConstraints: [NSLayoutConstraint] = []
override init(frame: CGRect) {
super.init(frame: frame)
Bundle.main.loadNibNamed("ConnectionLostAlertView", owner: self, options: nil)
self.addSubview(self.view)
view.translatesAutoresizingMaskIntoConstraints = false
let bottomConstraint = view.bottomAnchor.constraint(equalTo: self.bottomAnchor)
let leftConstraint = view.leadingAnchor.constraint(equalTo: self.leadingAnchor)
let rightConstraint = view.trailingAnchor.constraint(equalTo: self.trailingAnchor)
viewConstraints = [bottomConstraint, leftConstraint, rightConstraint]
NSLayoutConstraint.activate(viewConstraints)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
Bundle.main.loadNibNamed("ConnectionLostAlertView", owner: self, options: nil)
self.addSubview(self.view)
}
override func didMoveToSuperview() {
self.translatesAutoresizingMaskIntoConstraints = false
let bottomConstraint = self.bottomAnchor.constraint(equalTo: self.superview!.bottomAnchor)
let leftConstraint = self.leadingAnchor.constraint(equalTo: self.superview!.leadingAnchor)
let rightConstraint = self.trailingAnchor.constraint(equalTo: self.superview!.trailingAnchor)
viewConstraints = [bottomConstraint, leftConstraint, rightConstraint]
NSLayoutConstraint.activate(viewConstraints)
}
#IBAction func dismissButton(_ sender: Any) {
Logger.log(message: "Dismiss button was pressed", event: .d)
self.isHidden = true
}

Swift: Custom view hierarchy

I am attempting to build a cinema ticketing app and I need help with my view hierarchy. At the moment my cinemaView does not get added to the VC.
Here is how I attempt to do it. I have a custom seatView that has two properties (isVacant and seatNumber) and a custom cinemaView that has [seatView] as a property (well different cinema has different seatings). My code as such:
//In my viewController
class ViewController: UIViewController, UIScrollViewDelegate {
let scrollView = UIScrollView()
let cinema: CinemaView = {
let v = CinemaView()
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
scrollView.minimumZoomScale = 1.0
scrollView.maximumZoomScale = 10.0
scrollView.zoomScale = scrollView.minimumZoomScale
scrollView.delegate = self
scrollView.isScrollEnabled = true
scrollView.translatesAutoresizingMaskIntoConstraints = false
let seat1 = SeatView()
seat1.isVacant = false
seat1.seatNumber = "2A"
let seat2 = SeatView()
seat2.isVacant = true
seat2.seatNumber = "3B"
cinema.seats = [seat1, seat2]
view.addSubview(scrollView)
scrollView.addSubview(cinema)
let views = ["scrollView": scrollView, "v": cinema]
let screenHeight = view.frame.height
view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|[scrollView(\(screenHeight / 2))]", options: NSLayoutFormatOptions(), metrics: nil, views: views))
view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[scrollView]|", options: NSLayoutFormatOptions(), metrics: nil, views: views))
scrollView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|-60-[v(50)]", options: NSLayoutFormatOptions(), metrics: nil, views: views))
scrollView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[v]|", options: NSLayoutFormatOptions(), metrics: nil, views: views))
}
//At my custom cinemaView
class CinemaView: UIView {
var seats = [SeatView]()
var xPos: Int = 0
let cinemaView: UIView = {
let v = UIView()
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
override init(frame: CGRect) {
super.init(frame: frame)
addSubview(cinemaView)
cinemaView.backgroundColor = .black
let views = ["v": cinemaView]
addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|[v]|", options: NSLayoutFormatOptions(), metrics: nil, views: views))
addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[v]|", options: NSLayoutFormatOptions(), metrics: nil, views: views))
for seat in seats {
cinemaView.addSubview(seat)
seat.frame = CGRect(x: xPos, y: 0, width: 20, height: 20)
xPos += 8
}
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
//At my custom seatView
class SeatView: UIView {
var isVacant: Bool?
var seatNumber: String?
let seatView: UIView = {
let v = UIView()
v.frame = CGRect(x: 0, y: 0, width: 20, height: 20)
v.layer.cornerRadius = 5
return v
}()
override init(frame: CGRect) {
super.init(frame: frame)
addSubview(seatView)
seatView.backgroundColor = setupBackgroundColor()
}
func setupBackgroundColor() -> UIColor {
if let isVacant = isVacant {
if isVacant {
return UIColor.green
} else {
return UIColor.black
}
} else {
return UIColor.yellow
}
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
My code does not seem to add the cinemaView to my VC. Could anyone point me where have I gone wrong? Or perhaps even advice if this method is suitable for this application? Thanks.
You need to give the frames while creating any UIView.
override init(frame: CGRect) will be called when you specify the frame of your custom UIView.
I have created a similar hierarchy as yours as an example. Have a look at it.
Also xPos must be such that it does not overlap the previous SeatView, i.e (new xPos + previous SeatView width).
Also in your SeatView and CinemaView you are adding a UIView inside another UIView which is kind of redundant. You don't need to do that.
Example:
class ViewController: UIViewController
{
override func viewDidLoad()
{
super.viewDidLoad()
let seat1 = SeatView(frame: CGRect(x: 0, y: 0, width: 50, height: 50))
let seat2 = SeatView(frame: CGRect(x: 0, y: 0, width: 50, height: 50))
let cinema = CinemaView(frame: CGRect(x: 0, y: 0, width: 300, height: 300))
cinema.seats = [seat1, seat2]
self.view.addSubview(cinema)
}
}
class CinemaView: UIView
{
var seats = [SeatView](){
didSet{
for seat in seats
{
seat.frame.origin.x = xPos
xPos += 100
self.addSubview(seat)
}
}
}
var xPos: CGFloat = 0
override init(frame: CGRect)
{
super.init(frame: frame)
self.backgroundColor = .black
}
required init?(coder aDecoder: NSCoder)
{
fatalError("init(coder:) has not been implemented")
}
}
class SeatView: UIView
{
override init(frame: CGRect)
{
super.init(frame: frame)
self.backgroundColor = .red
}
required init?(coder aDecoder: NSCoder)
{
fatalError("init(coder:) has not been implemented")
}
}
Well, you are doing several things wrong here...
You are subclassing UIView, but in your custom view you are adding a UIView as a subview, and trying to treat that as your actual view.
In your CinemaView class, you are looping your array of "seats" ... but you are doing so before assigning the array, so no seats will ever be created.
Similarly, in SeatView, you are trying to set the background color based on the .isVacant property, but you are doing so before the property is set.
You are trying to use a UIScrollView but you do not have the constraints set up correctly.
Setting constraints with Visual Format may feel convenient or easy, but it has limits. In your specific case, you want your scroll view to be 1/2 the height of your main view. With VFL, you have to calculate and explicitly set the constant, which will also have to be re-calculated any time the frame changes. Using the scroll view's .heightAnchor.constraint, setting it equal to the .heightAnchor of the view, and setting multiplier: 0.5 gets you 50% without any calculations.
So, lots to understand and lots to think about. I've made some edits to your original code. This should be considered a "starting point" for you to learn from, not "drop-in finished code":
class CinemaViewController: UIViewController, UIScrollViewDelegate {
let scrollView: UIScrollView = {
let sv = UIScrollView()
sv.minimumZoomScale = 1.0
sv.maximumZoomScale = 10.0
sv.zoomScale = sv.minimumZoomScale
sv.isScrollEnabled = true
sv.translatesAutoresizingMaskIntoConstraints = false
return sv
}()
let cinema: CinemaView = {
let v = CinemaView()
v.translatesAutoresizingMaskIntoConstraints = false
v.backgroundColor = UIColor.black
return v
}()
override func viewDidLoad() {
super.viewDidLoad()
// set our background to yellow, so we can see where it is
view.backgroundColor = .yellow
// create 2 "SeatView" objects
let seat1 = SeatView()
seat1.isVacant = false
seat1.seatNumber = "2A"
let seat2 = SeatView()
seat2.isVacant = true
seat2.seatNumber = "3B"
// set the array of "seats" in the cinema view object
cinema.seats = [seat1, seat2]
// assign scroll view delegate
scrollView.delegate = self
// set scroll view background color, so we can see it
scrollView.backgroundColor = .blue
// add the scroll view to this view
view.addSubview(scrollView)
// pin scrollView to top, leading and trailing, with 8-pt padding
scrollView.topAnchor.constraint(equalTo: view.topAnchor, constant: 8.0).isActive = true
scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 8.0).isActive = true
scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -8.0).isActive = true
// set scrollView height to 50% of view height
scrollView.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.5).isActive = true
// add cinema view to the scroll view
scrollView.addSubview(cinema)
// pin cinema to top and leading of scrollView, with 8.0-pt padding
cinema.topAnchor.constraint(equalTo: scrollView.topAnchor, constant: 8.0).isActive = true
cinema.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor, constant: 8.0).isActive = true
// set the width of cinema to scrollView width -16.0 (leaves 8.0-pts on each side)
cinema.widthAnchor.constraint(equalTo: scrollView.widthAnchor, constant: -16.0).isActive = true
// cinema height set to constant of 60.0 (for now)
cinema.heightAnchor.constraint(equalToConstant: 60.0).isActive = true
// in order to use a scroll view, its .contentSize must be defined
// so, use the trailing and bottom anchor constraints of cinema to define the .contentSize
cinema.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor, constant: 0.0).isActive = true
cinema.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor, constant: 0.0).isActive = true
}
}
//At my custom cinemaView
class CinemaView: UIView {
// when the seats property is set,
// remove any existing seat views
// and add a new seat view for each seat
// Note: eventually, this will likely be done with Stack Views and / or constraints
// rather than calculated Rects
var seats = [SeatView]() {
didSet {
for v in self.subviews {
v.removeFromSuperview()
}
var seatRect = CGRect(x: 0, y: 0, width: 20, height: 20)
for seat in seats {
self.addSubview(seat)
seat.frame = seatRect
seatRect.origin.x += seatRect.size.width + 8
}
}
}
// we're not doing anything on init() - yet...
// un-comment the following if you need to add setup code
// see the similar functionality in SeatView class
/*
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
*/
// perform any common setup tasks here
func commonInit() -> Void {
//
}
}
//At my custom seatView
class SeatView: UIView {
// change the background color when .isVacant property is set
var isVacant: Bool = false {
didSet {
if isVacant {
self.backgroundColor = UIColor.green
} else {
self.backgroundColor = UIColor.red
}
}
}
// not used currently
var seatNumber: String?
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
// perform any common setup tasks here
func commonInit() -> Void {
self.layer.cornerRadius = 5
}
}

subclass of a generic UIViewController subclass isn't available in InterfaceBuilder [duplicate]

This question already has answers here:
Xcode 7 Swift 2 impossible to instantiate UIViewController subclass of generic UITableViewController
(2 answers)
Closed 6 years ago.
I have a generic UIViewController subclass that expects a type that is a subclass of UIView and also implements a specific protocol. I have a subclass of that generic UIViewController subclass. I have a view controller in a story board with it's type set to MyKeyboardAwareScrollViewController (the subclass of the generic UIViewController subclass). When I run my program I get a message:
Unknown class _TtC19ExploreCodeCoverage35MyKeyboardAwareScrollViewController in Interface Builder file.
Any ideas why/how to fix?
A protocol:
protocol MyProtocol{
func widgets() -> Void
func activeView() -> UIView?
}
A Generic View Controller Subclass (that requires a type that is a UIView implementing the protocol MyProtocol):
class KeyboardAwareScrollViewController<T:UIView where T:MyProtocol>: UIViewController {
private let titleView: UIView
private let contentView: T
private weak var scrollView: UIScrollView?
private weak var titleViewContainer: UIView?
init(titleView:UIView, contentView: T){
self.titleView = titleView
self.contentView = contentView
super.init(nibName: nil, bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
self.titleView = aDecoder.decodeObjectOfClass(UIView.self, forKey: "titleView")!
self.contentView = aDecoder.decodeObjectOfClass(T.self, forKey: "contentView")!
super.init(coder: aDecoder)
}
override func viewWillAppear(animated: Bool) {
NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyboardShown:", name: UIKeyboardWillShowNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyboardHidden:", name: UIKeyboardWillHideNotification, object: nil)
}
override func viewWillDisappear(animated: Bool) {
NSNotificationCenter.defaultCenter().removeObserver(self)
}
func keyboardShown(notification: NSNotification){
let kbSize = (notification.userInfo![UIKeyboardFrameEndUserInfoKey] as! NSValue).CGRectValue()
if let scrollView = scrollView{
let insets = UIEdgeInsets(top: 0.0, left: 0.0, bottom: kbSize.height, right: 0.0)
scrollView.contentInset = insets
scrollView.scrollIndicatorInsets = insets
if let activeField = self.contentView.activeView(){
let convertedFrame = scrollView.convertRect(activeField.superview!.bounds, fromView: activeField.superview!)
let offsetFrame = CGRectOffset(convertedFrame, 0.0, 19.0)
let frame = CGRectInset(offsetFrame, 0.0, 17.0)
scrollView.scrollRectToVisible(frame, animated: false)
}
}
}
func keyboardHidden(notification: NSNotification){
if let scrollView = scrollView{
let insets = UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0)
scrollView.contentInset = insets
scrollView.scrollIndicatorInsets = insets
}
}
override func loadView() {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = UIColor.brownColor()
let sv = UIScrollView()
sv.backgroundColor = UIColor.clearColor()
sv.opaque = false
sv.translatesAutoresizingMaskIntoConstraints = false
sv.alwaysBounceVertical = true
self.scrollView = sv
let titleContainer = buildTitleContainer(self.titleView)
view.addSubview(titleContainer)
view.addSubview(sv)
//peg scroll view to container.
view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|[scrollView]|", options:NSLayoutFormatOptions(rawValue: 0), metrics:nil, views: ["scrollView":sv]))
view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("|[scrollView]|", options:NSLayoutFormatOptions(rawValue: 0), metrics:nil, views: ["scrollView":sv]))
let contentViewWrapper = UIView()
contentViewWrapper.translatesAutoresizingMaskIntoConstraints = false
contentViewWrapper.addSubview(self.contentView)
contentViewWrapper.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|->=0-[contentView]|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: ["contentView": contentView]))
contentViewWrapper.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("|[contentView]|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: ["contentView": contentView]))
sv.addSubview(contentViewWrapper)
let viewsDictionary = [
"scrollView": sv,
"view": view,
"contentViewWrapper": contentViewWrapper
]
//peg contentView to be at least as tall as view.
view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|[contentViewWrapper(>=view)]|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: viewsDictionary))
//peg contentView to be equal to host view width
view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("|[contentViewWrapper(==view)]|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: viewsDictionary))
//peg title container to width of container.
view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("|[titleContainer]|",
options:NSLayoutFormatOptions(rawValue: 0),
metrics: nil,
views: ["titleContainer":titleContainer]))
//title view height.
view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|[titleContainer][contentView]",
options:NSLayoutFormatOptions(rawValue: 0),
metrics: nil,
views: ["titleContainer":titleContainer, "contentView":self.contentView]))
self.view = view
self.titleViewContainer = titleContainer
}
func buildTitleContainer(titleView:UIView) -> UIView{
let titleContainer = UIView()
titleContainer.translatesAutoresizingMaskIntoConstraints = false
titleContainer.backgroundColor = UIColor.orangeColor()
titleContainer.addSubview(titleView)
titleContainer.setContentCompressionResistancePriority(1000.0, forAxis: .Vertical)
titleContainer.addConstraint(NSLayoutConstraint(item: titleView, attribute: .CenterY, relatedBy: .Equal, toItem: titleContainer, attribute: .CenterY, multiplier: 1.0, constant: 0.0))
titleContainer.addConstraint(NSLayoutConstraint(item: titleView, attribute: .CenterX, relatedBy: .Equal, toItem: titleContainer, attribute: .CenterX, multiplier: 1.0, constant: 0.0))
return titleContainer;
}
}
A UIView subclass that implements the protocol:
class MyContentView:UIView, MyProtocol, UITextFieldDelegate{
weak var myActiveView:UIView?
init(){
super.init(frame: CGRectZero)
self.backgroundColor = UIColor.clearColor()
self.opaque = false
self.translatesAutoresizingMaskIntoConstraints = false
let cells = Array(1...12).map{_ in cellView() }
var cellsDictionary: Dictionary<String, UIView> = [:], cellsVerticalFormat = "V:|-"
for (idx, cell) in cells.enumerate(){
self.addSubview(cell)
self.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("|-[cell]-|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: ["cell": cell]))
let cellKey = "cell\(idx)"
cellsDictionary[cellKey] = cell
cellsVerticalFormat = cellsVerticalFormat + "[\(cellKey)]-"
}
cellsVerticalFormat = cellsVerticalFormat + "|"
self.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat(cellsVerticalFormat, options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: cellsDictionary))
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
func cellView() -> UIView{
let result = UIView()
result.backgroundColor = UIColor.magentaColor()
result.translatesAutoresizingMaskIntoConstraints = false
let text = UITextField()
text.translatesAutoresizingMaskIntoConstraints = false
result.addSubview(text)
text.delegate = self
result.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("|-[text]-|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: ["text": text]))
result.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-[text]-|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: ["text": text]))
return result
}
func widgets() {
print("\(MyContentView.self) widgets")
}
func activeView() -> UIView? {
return myActiveView
}
func textFieldShouldBeginEditing(textField: UITextField) -> Bool{
myActiveView = textField.superview!
return true
}
func textFieldDidEndEditing(textField: UITextField) {
if myActiveView === textField.superview! {
myActiveView = nil
}
}
func textFieldShouldEndEditing(textField: UITextField) -> Bool {
return true
}
func textFieldShouldReturn(textField: UITextField) -> Bool {
textField.resignFirstResponder()
return false
}
}
A KeyboardAwareViewController subclass:
class MyKeyboardAwareScrollViewController : KeyboardAwareScrollViewController<MyContentView>{
let myContentView: MyContentView = MyContentView()
let myTitleView: UILabel = {
let result = UILabel()
result.translatesAutoresizingMaskIntoConstraints = false
result.text = "Pretend This Says\nCohn-Reznick"
result.numberOfLines = 0
result.textAlignment = .Center
return result
}()
required init?(coder aDecoder: NSCoder) {
super.init(titleView: myTitleView, contentView:myContentView)
}
}
Answer is here: Xcode 7 Swift 2 impossible to instantiate UIViewController subclass of generic UITableViewController
TLDR; Objective-c and InterfaceBuilder can't "see" generic swift classes.

Can't add NSLayoutConstraints programmatically in Swift (no effect)

I've created a custom UIView subclass in Swift with a corresponding .xib file but am having trouble adding Auto Layout constraints during initialization. The view itself loads just fine, but adding layout constraints seems to have no effect on the view's layout, regardless of whether I add these constraints within init or viewDidMoveToSuperview or viewDidMoveToWindow, and even if I call setNeedsUpdateConstraints() / layoutIfNeeded() immediately after.
I can do this quite easily in Objective-C, so hopefully this is a very simple fix, but I can't figure out what I am doing wrong.
Here is my code:
var isLoading = false
class CustomSubview: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
private lazy var view: UIView! = { [unowned self] in
let bundle = NSBundle(forClass: self.dynamicType)
let nibName = String(CustomSubview.self)
let nib = UINib(nibName: nibName, bundle: bundle)
return nib.instantiateWithOwner(self, options: nil)[0] as! UIView
}()
private func setup() {
if (isLoading) {
return
}
isLoading = true
// self.view.frame = self.bounds // this works but I want to use Auto Layout
// self.view.autoresizingMask = [UIViewAutoresizing.FlexibleWidth, UIViewAutoresizing.FlexibleHeight] // this works but I want to use Auto Layout
self.translatesAutoresizingMaskIntoConstraints = false // no effect
addSubview(self.view)
let views = Dictionary(dictionaryLiteral: ("view", self.view))
let horizontalConstraints = NSLayoutConstraint.constraintsWithVisualFormat("H:|[view]|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: views)
self.addConstraints(horizontalConstraints)
NSLayoutConstraint.activateConstraints(horizontalConstraints) // this seems to do nothing
let verticalConstraints = NSLayoutConstraint.constraintsWithVisualFormat("V:|[view]|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: views)
self.addConstraints(verticalConstraints)
NSLayoutConstraint.activateConstraints(verticalConstraints) // this seems to do nothing
self.setNeedsUpdateConstraints()
self.layoutIfNeeded()
isLoading = false
}
}
I've reworked your class a bit to make it look a bit cleaner. I've omitted the loading Bool. Also I have no experience in creating views from a XIB file I pretty much left it almost untouched.
var isLoading = false
class CustomSubview: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
private lazy var customView: UIView = {
let bundle = NSBundle(forClass: self.dynamicType)
let nibName = String(CustomSubview.self)
let nib = UINib(nibName: nibName, bundle: bundle)
let customView = nib.instantiateWithOwner(self, options: nil)[0] as! UIView
customView.translatesAutoresizingMaskIntoConstraints = false
return customView
}()
private func setup() {
if (isLoading) {
return
}
isLoading = true
addSubview(self.customView)
let views = ["customView": customView]
addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|[customView]|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: views))
addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|[customView]|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: views))
isLoading = false
}
}
As for your aspect ratio constraint, you can set multiple constraint for your aspect ratio and set the active of those to true / false to switch between them.

Resources