automatic constraints to a label inside xib swift - ios

I have a nib file I created for a custom view to be loaded into a ViewController. The custom view has:
.background image that fills the whole view (I'm using it as a background)
.label inside that I need to be placed in a specific location based on the size of the background image.
I managed to resize the background image based on the size of the screen using AutoLayout in Storyboard. It can shrink with "Aspect Fit".
But I want to change the constraints of the label with the same ratio as the background image resized. Meaning, if the background image shrinks by half, I want the constraints of the label to change by half. This way, they will always show up at the same spot with respect to the image.
But the constraints I created in the storyboard for the label stay constant and I can not figure out how to manipulate the constraints for the label programatically.
here is the code for the custom class that defines the custom XIB view:
import UIKit
#IBDesignable class TestView: UIView {
var view: UIView!
var nibName = "TestView"
#IBOutlet weak var backImage: UIImageView!
#IBOutlet weak var upcomingLabel: UILabel!
#IBInspectable var Background: UIImage? {
get {
return backImage.image
}
set(Background) {
backImage.image = Background
}
}
#IBInspectable var maintenance: String? {
get {
return upcomingLabel.text
}
set(maintenance) {
upcomingLabel.text = maintenance
}
}
//MARK - Implement both CustomView initializers: init(coder:) and init(frame:)
override init(frame: CGRect) {
super.init(frame: frame)
xibSetup()
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
xibSetup()
}
func xibSetup() {
view = loadViewFromNib()
view.frame = bounds
addSubview(view)
}
func loadViewFromNib() -> UIView {
let bundle = NSBundle(forClass: self.dynamicType)
let nib = UINib(nibName: nibName, bundle: bundle)
let view = nib.instantiateWithOwner(self, options: nil) [0] as! UIView
return view
}
}
And here is how I'm adding the view in the View controller:
import UIKit
class TestVC: UIViewController {
#IBOutlet weak var customViewContainer: UIView!
override func viewDidLoad() {
super.viewDidLoad()
let customView = TestView()
customView.setTranslatesAutoresizingMaskIntoConstraints(false)
customViewContainer.addSubview(customView)
//Add constraint to the view
let viewDictionary = ["custom": customView, "view": view]
let constraintH = NSLayoutConstraint.constraintsWithVisualFormat("H:|[custom]", options: NSLayoutFormatOptions(0), metrics: nil, views: viewDictionary)
let constraintV = NSLayoutConstraint.constraintsWithVisualFormat("V:|[custom]", options: NSLayoutFormatOptions(0), metrics: nil, views: viewDictionary)
let constraintWidth = NSLayoutConstraint(item: customView, attribute: .Width, relatedBy: .Equal, toItem: customViewContainer, attribute: .Width, multiplier: 1, constant: 0)
let constraintHeight = NSLayoutConstraint(item: customView, attribute: .Height, relatedBy: .Equal, toItem: customViewContainer, attribute: .Height, multiplier: 1, constant: 0)
view.addConstraints(constraintH as [AnyObject])
view.addConstraints(constraintV as [AnyObject])
view.addConstraint(constraintWidth)
view.addConstraint(constraintHeight)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
I'm loosing my mind over this, help!

Related

UIGestureRecognizer not detected on subview

I made a UIView subclass and added a circle view on top right corner of the view. Then I added UIPanGestureRecognizer to the circle view.
The problem is that gesture is only recognized on left bottom part of circle where the circle is located over the super view.
How can I make entire circle to property detected gesture?
Following is entire class code of UIView subclass I made.
import UIKit
class ResizableImageView: UIView {
private let circleWidth: CGFloat = 40
var themeColor: UIColor = UIColor.magentaColor()
lazy var cornerCircle: UIView = {
let v = UIView()
v.layer.cornerRadius = self.circleWidth / 2
v.layer.borderWidth = 1
v.layer.borderColor = self.themeColor.CGColor
let panGesture = UIPanGestureRecognizer(target: self, action: #selector(buttonTouchMoved) )
v.addGestureRecognizer(panGesture)
return v
}()
// Init
override init(frame: CGRect) {
super.init(frame: frame)
setupSubviews()
configureSelf()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setupSubviews() {
// Add cornerButton to self and set auto layout
addSubview( cornerCircle )
addConstraintsWithFormat("H:[v0(\(circleWidth))]", views: cornerCircle) // Extension code for setting auto layout
addConstraintsWithFormat("V:[v0(\(circleWidth))]", views: cornerCircle)
addConstraint(NSLayoutConstraint(item: cornerCircle, attribute: .CenterX, relatedBy: .Equal, toItem: self, attribute: .Right, multiplier: 1, constant: 0))
addConstraint(NSLayoutConstraint(item: cornerCircle, attribute: .CenterY, relatedBy: .Equal, toItem: self, attribute: .Top, multiplier: 1, constant: 0))
}
func configureSelf() {
// Set border
layer.borderWidth = 1
layer.borderColor = themeColor.CGColor
}
// Gesture Event
func buttonTouchMoved(gestureRecognizer: UIPanGestureRecognizer) {
let point = gestureRecognizer.locationInView(self)
print(point)
}
}
ViewController
import UIKit
class ImageViewCheckController: UIViewController {
let imageView: ResizableImageView = {
let iv = ResizableImageView()
return iv
}()
override func viewDidLoad() {
super.viewDidLoad()
title = "ImageViewCheck"
view.backgroundColor = UIColor.whiteColor()
setupSubviews()
}
func setupSubviews() {
view.addSubview( imageView )
view.addConstraintsWithFormat("V:|-100-[v0(200)]", views: imageView)
view.addConstraintsWithFormat("H:|-50-[v0(100)]", views: imageView)
}
}
Normally, there is no touch on a subview outside the bounds of its superview.
To change this, you will have to override hitTest(point:withEvent:) on the superview to alter the way hit-testing works:
override func hitTest(point: CGPoint, withEvent e: UIEvent?) -> UIView? {
if let result = super.hitTest(point, withEvent:e) {
return result
}
for sub in self.subviews.reverse() {
let pt = self.convertPoint(point, toView:sub)
if let result = sub.hitTest(pt, withEvent:e) {
return result
}
}
return nil
}

Adding Constraint programmatically swift

Please i need help with the below, i just start learning how to design interface programmatically, after few tutorials i wanted to try something, then i got stucked
I am trying to achieve the below image
but this is what i got
this is my code below
class FeedsCell: UICollectionViewCell{
override init(frame: CGRect){
super.init(frame: frame)
setupViews()
}
let thumNailImageView : UIImageView = {
let imageView = UIImageView()
imageView.backgroundColor = UIColor.blue
return imageView
}()
let sourceName:UILabel = {
let srcLabel = UILabel()
srcLabel.backgroundColor = UIColor.purple
srcLabel.translatesAutoresizingMaskIntoConstraints = false
return srcLabel
}()
let separatorView: UIView = {
let view = UIView()
view.backgroundColor = UIColor.black
return view
}()
func setupViews(){
addSubview(thumNailImageView)
addSubview(separatorView)
addSubview(sourceName)
addConstraintsWithFormat(format: "H:|-16-[v0(194)]", views: thumNailImageView)
addConstraintsWithFormat(format: "V:|-16-[v0]-16-[v1(1)]|", views: thumNailImageView, separatorView)
addConstraintsWithFormat(format: "H:|[v0]|", views: separatorView)
//left Constriants
addConstraint(NSLayoutConstraint(item: sourceName, attribute: .left, relatedBy: .equal, toItem: thumNailImageView, attribute: .right, multiplier: 1, constant: 8))
//Right constraints
addConstraint(NSLayoutConstraint(item: sourceName, attribute: .right, relatedBy: .equal, toItem: thumNailImageView, attribute: .right, multiplier: 1, constant: 0))
addConstraintsWithFormat(format: "H:|-8-[v0]-8-|", views: sourceName)
addConstraintsWithFormat(format: "V:|-8-[v0(20)]", views: sourceName)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension UIView{
func addConstraintsWithFormat(format: String, views: UIView...){
var viewDictionary = [String: UIView]()
for(index, view) in views.enumerated(){
let key = "v\(index)"
view.translatesAutoresizingMaskIntoConstraints = false
viewDictionary[key] = view
}
addConstraints(NSLayoutConstraint.constraints(withVisualFormat: format, options: NSLayoutFormatOptions(), metrics: nil, views: viewDictionary))
}
}
I have been able to solve the issue using SnapKit, to achieve the image did something like this
let screenFrame = UIScreen.main.bounds
thumNailImageView.snp.makeConstraints { (make) in
make.width.equalTo(194)
make.top.equalTo(contentView).offset(20)
make.left.equalTo(contentView).offset(20)
make.bottom.equalTo(contentView).offset(-20)
}
sourceName.snp.makeConstraints { (make) in
make.width.equalTo(screenFrame.width/2 - 40 )
make.height.equalTo(20)
make.top.equalTo(contentView).offset(20)
make.left.equalTo(contentView).offset(screenFrame.width/2 + 20 )
}
postTitle.snp.makeConstraints { (make) in
make.width.equalTo(screenFrame.width/2 - 40 )
make.height.equalTo(30)
make.top.equalTo(sourceName).offset(30)
make.left.equalTo(contentView).offset(screenFrame.width/2 + 20 )
}
timeStamp.snp.makeConstraints { (make) in
make.width.equalTo(screenFrame.width/2 - 40 )
make.height.equalTo(10)
make.top.equalTo(postTitle).offset(40)
make.left.equalTo(contentView).offset(screenFrame.width/2 + 20 )
}
postContent.snp.makeConstraints { (make) in
make.width.equalTo(screenFrame.width/2 - 40 )
make.height.equalTo(60)
make.top.equalTo(timeStamp).offset(20)
make.left.equalTo(contentView).offset(screenFrame.width/2 + 20 )
}

Animation difference between UIView and UIButton

In my custom table view cell, I have 4 buttons with an animation (shown below). The problem is when I'm using an UIButton, the animation doesn't animate as I wanted. But when I use an UIView, it works exactly as how I want it.
The code is exactly the same with only the difference of using a different type of UIView.
This animation is using an UIButton:
This animation is using an UIView
To make things a bit more clear, the only thing I've replaced in the code is:
// Test with Buttons
let button1 = Button() // Subclass of UIButton
let button2 = Button()
// Test with UIViews
let button1 = UIView()
let button2 = UIView()
Question:
Can someone tell me why a UIButton behaves differently compared to a normal UIView?
Initially I thought by not posting the code, I could make the question easier to read as both tests are using exactly the same code (except for the "element" (element being UIView or UIButton), and thought perhaps the problem lies in the difference between the "elements". I realize now that this was my mistake.
My code:
class CustomView: UIView {
private var base: [NSLayoutConstraint] = []
private var open: [NSLayoutConstraint] = []
var buttons: [UIView] = []
private var active = false
override init(frame: CGRect) {
super.init(frame: frame)
let button1 = CustomButton(frame: CGRectZero, color: UIColor.yellowColor().CGColor)
let button2 = CustomButton(frame: CGRectZero, color: UIColor.redColor().CGColor)
// let button1 = UIView(); button1.backgroundColor = UIColor.yellowColor()
// let button2 = UIView(); button2.backgroundColor = UIColor.redColor()
let views = ["button1": button1, "button2": button2]
buttons = [button1, button2]
buttons.enumerate().forEach {
$0.element.translatesAutoresizingMaskIntoConstraints = false
addSubview($0.element)
addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|[button\($0.index + 1)]|", options: [], metrics: nil, views: views))
base += [NSLayoutConstraint(item: $0.element, attribute: .Width , relatedBy: .Equal, toItem: buttons.first!, attribute: .Width , multiplier: 1, constant: 0)]
}
open += [NSLayoutConstraint(item: buttons.last!, attribute: .Width, relatedBy: .Equal, toItem: buttons.first!, attribute: .Width, multiplier: 0.33, constant: 0)]
addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|[button1]-0.5-[button2]|", options: [], metrics: nil, views: views))
addConstraints(base)
backgroundColor = .blackColor()
clipsToBounds = true
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func changeState() {
removeConstraints(active ? open : base); addConstraints(active ? base : open)
active = !active
UIView.animateWithDuration(0.5, animations: { self.layoutIfNeeded() })
}
}
Solution:
After posting the code and accidentally changing the background color of the buttons, I noticed that it was behaving accordingly. This made me realize I was using a CAShapeLayer in the buttons which is causing the behaviour seen in the first animation. Now I know what to fix. If this post should be closed or deleted, please tell me so. Then I will delete the answer. And thanks for those who tried to help!
Without you providing more details the only thing I can do is guessing that the behaviour you are witnessing comes down to buttons being less "elastic" than views, in that they have a "natural" size determined by their content (title and/or image) and auto layout really likes enforcing that natural size (as you can see from IB's warnings).
If you need more help, you should provide more details. Are you actually using auto layout? What constraints? What are you animating? etc.

How can I add same uiview multiples times at different positions to a UIViewController?

I am trying to achieve this in swift.
So far I created my own custom view which is a subclass of UIView class:
class MyConnections: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
override func drawRect(rect: CGRect) {
// Drawing code
let context = UIGraphicsGetCurrentContext()
CGContextSetLineWidth(context, 1)
CGContextSetStrokeColorWithColor(context, UIColor.blackColor().CGColor)
let circle = CGRectMake(5, 60, 80, 80)
CGContextAddEllipseInRect(context, circle)
CGContextStrokePath(context)
CGContextSetFillColorWithColor(context, UIColor.whiteColor().CGColor)
CGContextFillEllipseInRect(context, circle)
}
}
This is my view controller where I add the above view as a subview:
let profile = MyConnections()
override func viewDidLoad() {
super.viewDidLoad()
profile.backgroundColor = UIColor.clearColor()
view.addSubview(profile)
self.profile.setTranslatesAutoresizingMaskIntoConstraints(false)
//constraints for the location button
let horizontalConstraint = NSLayoutConstraint(item: self.profile, attribute: NSLayoutAttribute.Leading, relatedBy: NSLayoutRelation.Equal, toItem: self.view, attribute: NSLayoutAttribute.Leading, multiplier: 1.0, constant: 10)
let verticalConstraint = NSLayoutConstraint(item: self.profile
, attribute: NSLayoutAttribute.Top, relatedBy: NSLayoutRelation.Equal, toItem: self.view, attribute: NSLayoutAttribute.Top, multiplier: 1.0, constant: 20)
let widthConstraint = NSLayoutConstraint(item: self.profile, attribute: NSLayoutAttribute.Width, relatedBy: NSLayoutRelation.Equal, toItem: nil, attribute: NSLayoutAttribute.NotAnAttribute, multiplier: 1.0, constant: 150)
let heightConstraint = NSLayoutConstraint(item: self.profile, attribute: NSLayoutAttribute.Height, relatedBy: NSLayoutRelation.Equal, toItem: nil, attribute: NSLayoutAttribute.NotAnAttribute, multiplier: 1.0, constant: 150)
self.view.addConstraints([verticalConstraint, horizontalConstraint, widthConstraint, heightConstraint])
// Do any additional setup after loading the view.
}
All the code above gives me a circle on top. Now I want to repeat that same circle multiple times at different positions as seen in the image. I can create multiple instances of the uiview add them as subview but every time I will have to define new constraints for it which I don't want to do.
Can anyone please help me and give me an efficient answer?
You should know a UIView can have a single superview/parent. If you add it as a subview at a different position (using addSubview method) it will be removed from the first position and added as a subview to the new position.
In your case to add more subviews you have to create more UIView objects not use a single global UIView.
If the layout is repetitive a UITableView / UICollectionView is a better choice.
Your requirement and UI qualify for a UICollectionView I think you should use UICollectionView and can create a custom UICollectionViewCell with round image and and badge view as well, and they add dataSource and delegate methods. That will not only help in creating UI but it will make your app more performant by reusing cells
Here is a nice tutorial about UICollectionView
Here's a simplified view to create collection view programmatically:
make the collection view and layout programmatically just like any other view you would code and add it as subview like below:
lazy var myCollectionView : UICollectionView = {
let layout = YourFlowLayout()
layout.scrollDirection = self.direction;
layout.minimumInteritemSpacing = 0
layout.minimumLineSpacing = 0
let cv = UICollectionView(frame: CGRect.zero, collectionViewLayout: layout)
cv.dataSource = self
cv.delegate = self
cv.isPagingEnabled = true
cv.backgroundColor = UIColor.clear
cv.showsHorizontalScrollIndicator = false
cv.showsVerticalScrollIndicator = false
cv.allowsMultipleSelection = false
return cv
}()
and your flow layout could be something like:
mport UIKit
class Yourflowlayout: UICollectionViewFlowLayout {
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
return super.layoutAttributesForElements(in: rect)?.map {
attrs in
let attrscp = attrs.copy() as! UICollectionViewLayoutAttributes
self.applyLayoutAttributes(attributes: attrscp)
return attrscp
}
}
override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
if let attrs = super.layoutAttributesForItem(at: indexPath as IndexPath) {
let attrscp = attrs.copy() as! UICollectionViewLayoutAttributes
self.applyLayoutAttributes(attributes: attrscp)
return attrscp
}
return nil
}
func applyLayoutAttributes(attributes : UICollectionViewLayoutAttributes) {
if attributes.representedElementKind != nil {
return
}
if let collectionView = self.collectionView {
let stride = (self.scrollDirection == .horizontal) ? collectionView.frame.size.width : collectionView.frame.size.height
let offset = CGFloat(attributes.indexPath.section) * stride
var xCellOffset : CGFloat = CGFloat(attributes.indexPath.item) * self.itemSize.width
var yCellOffset : CGFloat = CGFloat(attributes.indexPath.item) * self.itemSize.height
if(self.scrollDirection == .horizontal) {
xCellOffset += offset;
} else {
yCellOffset += offset
}
attributes.frame = CGRect(x: xCellOffset, y: yCellOffset, width: self.itemSize.width, height: self.itemSize.height)
}
}
}
You can add the collectionView in your other classes as a subview , make sure you have the
myCollectionView.translatesAutoresizingMaskIntoConstraints = false so that your constrains are applied and you actually see the collection view and of course add your constrains or give it a frame.
Hope that helps someone.

Swift subclass UIView

I want to subclass UIView and show a login like view. I've created this in Objective-C, but now I want to port it to Swift. I do not use storyboards, so I create all my UI in code.
But the first problem is that I must implement initWithCoder. I gave it a default implementation since It won't be called. Now when I run the program it crashes, because I've to implement initWithFrame as well. Now I got this:
override init() {
super.init()
println("Default init")
}
override init(frame: CGRect) {
super.init(frame: frame)
println("Frame init")
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
println("Coder init")
}
My question is where should I create my textfield etc... and if I never implement frame and coder how can I "hide" this?
I usually do something like this, its a bit verbose.
class MyView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
addBehavior()
}
convenience init() {
self.init(frame: CGRect.zero)
}
required init(coder aDecoder: NSCoder) {
fatalError("This class does not support NSCoding")
}
func addBehavior() {
print("Add all the behavior here")
}
}
let u = MyView(frame: CGRect.zero)
let v = MyView()
(Edit: I've edited my answer so that the relation between the initializers is more clear)
This is more simple.
override init (frame : CGRect) {
super.init(frame : frame)
// Do what you want.
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
Custom UIView Subclass Example
I usually create iOS apps without using storyboards or nibs. I'll share some techniques I've learned to answer your questions.
Hiding Unwanted init Methods
My first suggestion is to declare a base UIView to hide unwanted initializers. I've discussed this approach in detail in my answer to "How to Hide Storyboard and Nib Specific Initializers in UI Subclasses". Note: This approach assumes you will not use BaseView or its descendants in storyboards or nibs since it will intentionally cause the app to crash.
class BaseView: UIView {
// This initializer hides init(frame:) from subclasses
init() {
super.init(frame: CGRect.zero)
}
// This attribute hides `init(coder:)` from subclasses
#available(*, unavailable)
required init?(coder aDecoder: NSCoder) {
fatalError("NSCoding not supported")
}
}
Your custom UIView subclass should inherit from BaseView. It must call super.init() in its initializer. It does not need to implement init(coder:). This is demonstrated in the example below.
Adding a UITextField
I create stored properties for subviews referenced outside of the init method. I would typically do so for a UITextField. I prefer to instantiate subviews within the declaration of the subview property like this: let textField = UITextField().
The UITextField will not be visible unless you add it to the custom view's subview list by calling addSubview(_:). This is demonstrated in the example below.
Programmatic Layout Without Auto Layout
The UITextField will not be visible unless you set its size and position. I often do layout in code (not using Auto Layout) within the layoutSubviews method. layoutSubviews() is called initially and whenever a resize event happens. This allows adjusting layout depending on the size of CustomView. For example, if CustomView appears the full width on various sizes of iPhones and iPads and adjusts for rotation, it needs to accommodate many initial sizes and resize dynamically.
You can refer to frame.height and frame.width within layoutSubviews() to get the CustomView's dimensions for reference. This is demonstrated in the example below.
Example UIView Subclass
A custom UIView subclass containing a UITextField which does not need to implement init?(coder:).
class CustomView: BaseView {
let textField = UITextField()
override init() {
super.init()
// configure and add textField as subview
textField.placeholder = "placeholder text"
textField.font = UIFont.systemFont(ofSize: 12)
addSubview(textField)
}
override func layoutSubviews() {
super.layoutSubviews()
// Set textField size and position
textField.frame.size = CGSize(width: frame.width - 20, height: 30)
textField.frame.origin = CGPoint(x: 10, y: 10)
}
}
Programmatic Layout with Auto Layout
You can also implement layout using Auto Layout in code. Since I don't often do this, I will not show an example. You can find examples of implementing Auto Layout in code on Stack Overflow and elsewhere on the Internet.
Programmatic Layout Frameworks
There are open source frameworks that implement layout in code. One I am interested in but have not tried is LayoutKit. It was written by the development team an LinkedIn. From the Github repository: "LinkedIn created LayoutKit because we have found that Auto Layout is not performant enough for complicated view hierarchies in scrollable views."
Why put fatalError in init(coder:)
When creating UIView subclasses that will never be used in a storyboard or nib, you might introduce initializers with different parameters and initialization requirements that could not be called by the init(coder:) method. If you did not fail init(coder:) with a fatalError, it could lead to very confusing problems down the line if accidentally used in a storyboard/nib. The fatalError asserts these intentions.
required init?(coder aDecoder: NSCoder) {
fatalError("NSCoding not supported")
}
If you want to run some code when the subclass is created regardless of whether it is created in code or a storyboard/nib then you could do something like the following (based on Jeff Gu Kang’s answer)
class CustomView: UIView {
override init (frame: CGRect) {
super.init(frame: frame)
initCommon()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
initCommon()
}
func initCommon() {
// Your custom initialization code
}
}
It's important that your UIView can be created by interface builder/storyboards or from code. I find it's useful to have a setup method to reduce duplicating any setup code. e.g.
class RedView: UIView {
override init (frame: CGRect) {
super.init(frame: frame)
setup()
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
setup()
}
func setup () {
backgroundColor = .red
}
}
Swift 4.0,If you want to use view from xib file, then this is for you. I created CustomCalloutView class Sub class of UIView. I have created a xib file and in IB just select file owner then select Attribute inspector set class name to CustomCalloutView, then create outlet in your class.
import UIKit
class CustomCalloutView: UIView {
#IBOutlet var viewCallout: UIView! // This is main view
#IBOutlet weak var btnCall: UIButton! // subview of viewCallout
#IBOutlet weak var btnDirection: UIButton! // subview of viewCallout
#IBOutlet weak var btnFavourite: UIButton! // subview of viewCallout
// let nibName = "CustomCalloutView" this is name of xib file
override init(frame: CGRect) {
super.init(frame: frame)
nibSetup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
nibSetup()
}
func nibSetup() {
Bundle.main.loadNibNamed(String(describing: CustomCalloutView.self), owner: self, options: nil)
guard let contentView = viewCallout else { return } // adding main view
contentView.frame = self.bounds //Comment this line it take default frame of nib view
// custom your view properties here
self.addSubview(contentView)
}
}
// Now adding it
let viewCustom = CustomCalloutView.init(frame: CGRect.init(x: 120, y: 120, 50, height: 50))
self.view.addSubview(viewCustom)
Here's an example of how I usually build my subclasses(UIView). I have the content as variables so they can be accessed and tweaked maybe later in some other class. I've also shown how I use auto layout and adding content.
For example in a ViewController I have this view initialized In ViewDidLoad() since that is only called once when the view is visible. Then I use these functions I make here addContentToView() and then activateConstraints() to build the content and set constraints. If I later in a ViewController want the color of let's say a button to be red, I just do that in that specific function in that ViewController.
Something like: func tweaksome(){ self.customView.someButton.color = UIColor.red}
class SomeView: UIView {
var leading: NSLayoutConstraint!
var trailing: NSLayoutConstraint!
var bottom: NSLayoutConstraint!
var height: NSLayoutConstraint!
var someButton: UIButton = {
var btn: UIButton = UIButton(type: UIButtonType.system)
btn.setImage(UIImage(named: "someImage"), for: .normal)
btn.translatesAutoresizingMaskIntoConstraints = false
return btn
}()
var btnLeading: NSLayoutConstraint!
var btnBottom: NSLayoutConstraint!
var btnTop: NSLayoutConstraint!
var btnWidth: NSLayoutConstraint!
var textfield: UITextField = {
var tf: UITextField = UITextField()
tf.adjustsFontSizeToFitWidth = true
tf.placeholder = "Cool placeholder"
tf.translatesAutoresizingMaskIntoConstraints = false
tf.backgroundColor = UIColor.white
tf.textColor = UIColor.black
return tf
}()
var txtfieldLeading: NSLayoutConstraint!
var txtfieldTrailing: NSLayoutConstraint!
var txtfieldCenterY: NSLayoutConstraint!
override init(frame: CGRect){
super.init(frame: frame)
self.translatesAutoresizingMaskIntoConstraints = false
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
//fatalError("init(coder:) has not been implemented")
}
/*
// Only override draw() if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
override func draw(_ rect: CGRect) {
// Drawing code
}
*/
func activateConstraints(){
NSLayoutConstraint.activate([self.btnLeading, self.btnBottom, self.btnTop, self.btnWidth])
NSLayoutConstraint.activate([self.txtfieldCenterY, self.txtfieldLeading, self.txtfieldTrailing])
}
func addContentToView(){
//setting the sizes
self.addSubview(self.userLocationBtn)
self.btnLeading = NSLayoutConstraint(
item: someButton,
attribute: .leading,
relatedBy: .equal,
toItem: self,
attribute: .leading,
multiplier: 1.0,
constant: 5.0)
self.btnBottom = NSLayoutConstraint(
item: someButton,
attribute: .bottom,
relatedBy: .equal,
toItem: self,
attribute: .bottom,
multiplier: 1.0,
constant: 0.0)
self.btnTop = NSLayoutConstraint(
item: someButton,
attribute: .top,
relatedBy: .equal,
toItem: self,
attribute: .top,
multiplier: 1.0,
constant: 0.0)
self.btnWidth = NSLayoutConstraint(
item: someButton,
attribute: .width,
relatedBy: .equal,
toItem: self,
attribute: .height,
multiplier: 1.0,
constant: 0.0)
self.addSubview(self.textfield)
self.txtfieldLeading = NSLayoutConstraint(
item: self.textfield,
attribute: .leading,
relatedBy: .equal,
toItem: someButton,
attribute: .trailing,
multiplier: 1.0,
constant: 5)
self.txtfieldTrailing = NSLayoutConstraint(
item: self.textfield,
attribute: .trailing,
relatedBy: .equal,
toItem: self.doneButton,
attribute: .leading,
multiplier: 1.0,
constant: -5)
self.txtfieldCenterY = NSLayoutConstraint(
item: self.textfield,
attribute: .centerY,
relatedBy: .equal,
toItem: self,
attribute: .centerY,
multiplier: 1.0,
constant: 0.0)
}
}

Resources