Custom View with Xib as root View - ios

When creating a custom view, is it possible to set the root view as the xib?
Currently if I create a custom view, I need to add the nib as a subview.
If I want to change the background color, I can no longer use the UIView APIs.
customView.backgroundColor = UIColor.red // Won't work because nib view is covering the "root" view.
So, is it possible to do this..
class CustomView: UIView {
func commonInit() {
let bundle = Bundle(for: type(of: self))
let nib = UINib(nibName: getInheritedClassName(object: type(of: self)), bundle: bundle)
let view = nib.instantiate(withOwner: self, options: nil).first as! UIView
// Currently doing this..
addSubview(view)
// Can we do this somehow?
self = view // Set self as the xib view
}
}

Use awakeAfter:
open override func awakeAfter(using _: NSCoder) -> Any? {
// set the tag in the view's XIB, so we don't create an infinite loop
guard tag != 999 else {
return self
}
let view = type(of: self).instantiate() // Convenience method that chooses nib based on class name
view.frame = frame
view.autoresizingMask = autoresizingMask
view.translatesAutoresizingMaskIntoConstraints = translatesAutoresizingMaskIntoConstraints
view.tag = tag
for constraint in constraints {
let firstItem = constraint.firstItem as? UIView == self ? view : constraint.firstItem
let secondItem = constraint.secondItem as? UIView == self ? view : constraint.secondItem
let constraintToAdd = NSLayoutConstraint(item: firstItem as Any, attribute: constraint.firstAttribute, relatedBy: constraint.relation, toItem: secondItem, attribute: constraint.secondAttribute, multiplier: constraint.multiplier, constant: constraint.constant)
constraintToAdd.priority = constraint.priority
view.addConstraint(constraintToAdd)
}
return view
}

Related

error while creating custom view using XIB

I've created a custom view and interface using xib.
class CardStatus: UIView{
override func awakeFromNib() {
super.awakeFromNib()
let bundle = Bundle(for: CardStatus.self)
let nib = UINib(nibName: "CardStatus", bundle: bundle)
guard let view = nib.instantiate(withOwner: self, options: nil).first as? UIView else { fatalError("Error loading \(self) from nib") }
addSubview(view)
view.translatesAutoresizingMaskIntoConstraints = false
}
}
xib looks like below:
in the storyboard, I've set the class as my custom one like below.
the error is like below:
its going in infinite loop.
I tried the below way also
class CardStatus: UIView{
required init?(coder aDecoder: NSCoder)() {
super.init(coder: aDecoder)
let bundle = Bundle(for: CardStatus.self)
let nib = UINib(nibName: "CardStatus", bundle: bundle)
guard let view = nib.instantiate(withOwner: self, options: nil).first as? UIView else { fatalError("Error loading \(self) from nib") }
addSubview(view)
view.translatesAutoresizingMaskIntoConstraints = false
}
}

I got animation on custom view which belongs to uitableviewcell and only the first one is working

I've a custom view that is an outlet in prototype cell.
In the view, i make an animation on certain constraint constant.
This animation works only for the first cell, but not on the other cells.
override open func awakeFromNib() {
super.awakeFromNib()
self.xibSetup()
self.setViews()
}
private func animateTitleUp(){
if self.titleTopConstraint.constant == self.titleOriginConstant{
self.titleTopConstraint.constant -= self.titleChangeConstant
self.title.alpha = 1
UIView.animate(withDuration: 4, animations:{ [weak self]() in
self?.contentView?.layoutIfNeeded()
})
}
}
func xibSetup() {
guard let view = loadViewFromNib() else { return }
view.frame = bounds
self.contentView = view
addSubview(self.contentView!)
}
func loadViewFromNib() -> UIView? {
setBundle()
let nib = UINib(nibName: "CustomPicker", bundle: bundle)
return nib.instantiate(
withOwner: self,
options: nil).first as? UIView
}

unable to show view in centre if not data found in tableview iOS?

I have a tableview in which I am showing data coming from server. Now If there is no data coming then I show a UIView which has a label and image which show no data found . I have created a xib file for view. Now when I get view from Xib and show as tableview background view then it does not show in centre. it shows on top.
let emptyListView = EmptyListView.instanceFromNib() as! EmptyListView
emptyListView.labelNothingFound.text = AppMessages.NoDataFound.noRestaurentFound
emptyListView.center = tableView.center
tableView.backgroundView = emptyListView
tableView.separatorStyle = .none
I also tried giving custom frames but it does not work. Please tell me what is the issue ?
I faced same issue and after alot of stuggle i comeup with this logic.
Create custom view .xib file, add image and label as shown in screenshot.
And add following code in its swift file and link outlets.
import UIKit
class NoJobsViews: UIView {
#IBOutlet weak var imageView: UIImageView!
#IBOutlet weak var messageLabel: UILabel!
class func instanceFromNib() -> NoJobsViews {
return UINib(nibName: "NoJobsViews", bundle: nil).instantiate(withOwner: nil, options: nil)[0] as! NoJobsViews
}
/*
// Only override draw() if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
override func draw(_ rect: CGRect) {
// Drawing code
}
*/
}
Add following method in your Utility class or in extension of UIViewController
class func emptyTableViewMessageWithImage(message:String,image: String, viewController: UIViewController, tableView: UITableView) {
let noJobsView = EmptyTableViewBackgroundView.instanceFromNib()
noJobsView.imageView.image = UIImage(named: image)
noJobsView.messageLabel.text = message
tableView.backgroundView = noJobsView
tableView.separatorStyle = .none
}
Now write following lines of code in your TableView numberofRows Datasource method
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if dataSource.count == 0 {
Utility.emptyTableViewMessageWithImage(message: "No Job Found", image: kNoMyGig , viewController: self, tableView: tableView)
return 0
}
tableView.backgroundView = UIView()
return dataSource.count
}
You better make extension to UIViewController, that will make a sense so that you can use that xib where ever listing occurs.
Custom UIView class
import UIKit
class NoDataFound: UIView {
#IBOutlet var noDataLbl: UILabel!
var view = UIView()
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
override func awakeFromNib()
{
super.awakeFromNib()
}
func setup() {
// setup the view from .xib
view = loadViewFromNib()
//view.frame = bounds
// print(view.bounds)
self.frame = view.bounds
self .isUserInteractionEnabled = true
view.backgroundColor = UIColor .black
view.autoresizingMask = [UIViewAutoresizing.flexibleWidth, UIViewAutoresizing.flexibleHeight]
addSubview(view)
}
func loadViewFromNib() -> UIView {
// grabs the appropriate bundle
let bundle = Bundle(for: type(of: self))
let nib = UINib(nibName: "NoDataFound", bundle: bundle)
let view = nib.instantiate(withOwner: self, options: nil)[0] as! UIView
return view
}
func showContent(msg:String){
self.noDataLbl.text = msg
}
}
Here i have created only lable in xib, u can add more UI elements, thats depends upon you.
Extension
import UIKit
extension UIViewController {
func showNoDataFound(currentView:UIView,content:String){
let noView = NoDataFound()
noView.tag = 12346
noView.translatesAutoresizingMaskIntoConstraints = false
noView.backgroundColor = UIColor.black
noView.showContent(msg: content)
currentView .addSubview(noView)
NSLayoutConstraint(item: noView, attribute: NSLayoutAttribute.centerX, relatedBy: NSLayoutRelation.equal, toItem: currentView, attribute: NSLayoutAttribute.centerX, multiplier: 1, constant: 0).isActive = true
NSLayoutConstraint(item: noView, attribute: NSLayoutAttribute.centerY, relatedBy: NSLayoutRelation.equal, toItem: currentView, attribute: NSLayoutAttribute.centerY, multiplier: 1, constant: 0).isActive = true
}
func removeNoviewTag(currentView:UIView){
for view in currentView .subviews{
if view.tag == 12346{
view .removeFromSuperview()
}
}
}
}
After your server response parsing
Call like below where ever you want to show no data found
if self.newsListObj.count == 0 { //your server array count
self.removeNoviewTag(currentView: self.view)
self.showNoDataFound(currentView: self.view, content: Contants.NoSavedArticleMessage)
self.favCollectionView.isHidden = true //your list table or collection view
} else {
self.removeNoviewTag(currentView: self.view)
self.favCollectionView.isHidden = false
// reload your collection or table
}

How to do initial styling of labels and UILabels/UIImageViews in a sub view which is loaded via an XIB in a UITableViewCell

I have a UITableViewCell which has a subview (inside its content view) which contains labels, and an image view and other properties I need to set.
This subview is set from an XIB as it is used elsewhere in the app. I am loading it into the cell with
private func setup() {
let nib = UINib.init(nibName: "AuthorHeaderView", bundle: nil)
if let view = nib.instantiate(withOwner: self, options: nil).first as? UIView {
view.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(view)
view.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
view.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
view.topAnchor.constraint(equalTo: topAnchor).isActive = true
view.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
}
}
Then, when the cell is loaded I am setting some default values in awakeFromNib(). When I have a breakpoint there, I can see that the headerView: AuthorHeaderView has been loaded into memory and is set up correctly, but it's labels and imageView haven't been, they are nil, and thus it crashes when trying to style these views.
How to do initial styling of labels and image views and everything in a sub view which is loaded via an XIB?
OK, so I found the solution out. The problem was that I hadn't added init(coder aDecoder: NSCoder)
I have a setup method that was being called from awakeFromNib, but I also needed to call it from initWithCoder.
Example:
override func awakeFromNib() {
super.awakeFromNib()
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
private func setup() {
let nib = UINib.init(nibName: "HeaderView", bundle: nil)
if let view = nib.instantiate(withOwner: self, options: nil).first as? UIView {
view.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(view)
view.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
view.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
view.topAnchor.constraint(equalTo: topAnchor).isActive = true
view.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
}
}
Anyway, now it works perfectly. Hope this helps someone else at some stage :-)

Assign xib to the UIView in Swift

in objective c it can be done in init method by
-(id)init{
self = [[[NSBundle mainBundle] loadNibNamed:#"ViewBtnWishList" owner:0 options:nil] objectAtIndex:0];
return self;
}
but when i do this in swift
init(frame: CGRect) {
self = NSBundle.mainBundle().loadNibNamed("ViewDetailMenu", owner: 0, options: nil)[0] as? UIView
}
cannot assign to self in a method error is shown.
now my approach is to create a view, and add the view loaded from nib to it.
anyone have a better idea?
for Swift 4
extension UIView {
class func loadFromNibNamed(nibNamed: String, bundle: Bundle? = nil) -> UIView? {
return UINib(
nibName: nibNamed,
bundle: bundle
).instantiate(withOwner: nil, options: nil)[0] as? UIView
}
}
for Swift 3
You could create an extension on UIView:
extension UIView {
class func loadFromNibNamed(nibNamed: String, bundle: NSBundle? = nil) -> UIView? {
return UINib(
nibName: nibNamed,
bundle: bundle
).instantiateWithOwner(nil, options: nil)[0] as? UIView
}
}
Note: Using UINib is faster because it does caching for you.
Then you can just do:
ViewDetailItem.loadFromNibNamed("ViewBtnWishList")
And you will be able to reuse that method on any view.
This worked for me.
override func awakeAfterUsingCoder(aDecoder: NSCoder) -> AnyObject? {
if self.subviews.count == 0 {
return loadNib()
}
return self
}
private func loadNib() -> YourCustomView {
return NSBundle.mainBundle().loadNibNamed("YourCustomViewNibName", owner: nil, options: nil)[0] as YourCustomView
}
Tested in Xcode 7 beta 4 , Swift 2.0 .
The following code will assign xib to the UIView.
You can use this custom xib view in storyboard and access the IBOutlet object also.
import UIKit
#IBDesignable class SimpleCustomView:UIView
{
var view:UIView!;
#IBOutlet weak var lblTitle: UILabel!
#IBInspectable var lblTitleText : String?
{
get{
return lblTitle.text;
}
set(lblTitleText)
{
lblTitle.text = lblTitleText!;
}
}
override init(frame: CGRect) {
super.init(frame: frame)
loadViewFromNib ()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
loadViewFromNib ()
}
func loadViewFromNib() {
let bundle = NSBundle(forClass: self.dynamicType)
let nib = UINib(nibName: "SimpleCustomView", bundle: bundle)
let view = nib.instantiateWithOwner(self, options: nil)[0] as! UIView
view.frame = bounds
view.autoresizingMask = [.FlexibleWidth, .FlexibleHeight]
self.addSubview(view);
}
}
Access customview programatically
self.customView = SimpleCustomView(frame: CGRectMake(100, 100, 200, 200))
self.view.addSubview(self.customView!);
Source code - https://github.com/karthikprabhuA/CustomXIBSwift
that may be a solution for you:
Swift 3.x
class func instanceFromNib() -> UIView {
return UINib(nibName: "<<NibFileName>>", bundle: nil).instantiate(withOwner: nil, options: nil)[0] as! UIView
}
Swift 2.x
class func instanceFromNib() -> UIView {
return UINib(nibName: "<<NibFileName>>", bundle: nil).instantiateWithOwner(nil, options: nil)[0] as UIView
}
I think this is the easies but also the cleanest way to assign a xib to a UIView. Xcode 7.3 and swift 2.0.
import UIKit
//Create CustomView class
class CustomView: UIView {
class func instanceFromNib() -> UIView {
return UINib(nibName: "CustomView", bundle: nil).instantiateWithOwner(nil, options: nil)[0] as! UIView
}
}
//Use it
let customView = CustomView.instanceFromNib() as! CustomView
The true Swift approach is the use of protocols and protocol extensions.
I use it like this: To start I create a protocol
protocol XibInitializable {
static var name: String { get }
static var bundle: Bundle? { get }
static func fromXib() -> Self
}
then I make a default implementation of this protocol use protocol extention
extension XibInitializable where Self : UIView {
static var name: String {
return String(describing: Self.self)
}
static var bundle: Bundle? {
return nil
}
static func fromXib() -> Self {
return UINib(nibName: name, bundle: bundle).instantiate(withOwner: nil, options: nil)[0] as! Self
}
}
the implementation of our protocol is now complete
In order for this protocol to work, you need the name of our xib file and the class were the same. For example, for example
finally add the protocol and make your class "final", like here.
That's it
and use
instead of adding an extension to UIView, you could define a protocol and add the implementation to a protocol extension. You can then declare that UIView conforms to the protocol.
This allows the return type to be Self instead of UIView. So the caller doesn't have to cast to the class.
Explained here:
https://stackoverflow.com/a/33424509/845027
import UIKit
protocol UIViewLoading {}
extension UIView : UIViewLoading {}
extension UIViewLoading where Self : UIView {
// note that this method returns an instance of type `Self`, rather than UIView
static func loadFromNib() -> Self {
let nibName = "\(self)".characters.split{$0 == "."}.map(String.init).last!
let nib = UINib(nibName: nibName, bundle: nil)
return nib.instantiateWithOwner(self, options: nil).first as! Self
}
}
Just made a UINib extension to load a view from xib and embed into a container view using constraints, using generics and strong naming (without using Strings, assuming you have the same file name for xib and implementation):
extension UINib {
static func instantiateViewAndEmbedWithConstraints <T: UIView> (viewType viewType: T.Type, embedInto containerView: UIView) -> T {
let view = UINib(nibName: String(viewType), bundle: nil).instantiateWithOwner(nil, options: nil).first as! T
containerView.addSubview(view)
view.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint(item: view, attribute: .Leading, relatedBy: .Equal, toItem: containerView, attribute: .Leading, multiplier: 1, constant: 0).active = true
NSLayoutConstraint(item: view, attribute: .Trailing, relatedBy: .Equal, toItem: containerView, attribute: .Trailing, multiplier: 1, constant: 0).active = true
NSLayoutConstraint(item: view, attribute: .Top, relatedBy: .Equal, toItem: containerView, attribute: .Top, multiplier: 1, constant: 0).active = true
NSLayoutConstraint(item: view, attribute: .Bottom, relatedBy: .Equal, toItem: containerView, attribute: .Bottom, multiplier: 1, constant: 0).active = true
return view
}
}
Usage:
...outlets...
#IBOutlet var containerView: UIView!
var customView: CustomView!
...viewDidLoad...
customView = UINib.instantiateViewAndEmbedWithConstraints(viewType: CustomView.self, embedInto: containerView)
Just subclass this simple class (swift 5):
open class NibView: UIView {
open override func awakeAfter(using coder: NSCoder) -> Any? {
if subviews.count == 0 {
return UINib(nibName: "\(Self.self)", bundle: nil).instantiate(withOwner: nil, options: nil)[0] as! UIView
}
return self
}
}
class CustomView: NibView {
}
As others pointed out, set File's Owner to your CustomView class (not xib's root view itself). Then set custom class to CustomView to any view that you want to be replaced by your custom view class. Also, autolayout respects all constraints inside your xib, at least as a subview of a UITableViewCell's content view. Not sure about other cases.
As simple, as it happens to be, somehow Apple did another quest for us for such a basic thing! What a wonderful company! Never gonna be bored with them!

Resources