I would like to add a custom UIView. The class definition of my custom view is:
class UserCoinView: UIView {
#IBOutlet weak var userName: UILabel!
#IBOutlet weak var coinView: UIView!
#IBOutlet weak var coinAmount: UILabel!
#IBOutlet weak var coinIcon: UILabel!
override func drawRect(rect: CGRect) {
let smartCoins = SmartShopperUtil.getSmartShopper().smartCoins
if smartCoins != nil && smartCoins >= 0 {
coinAmount.text = String(smartCoins!)
coinView.backgroundColor = SmartShopperUtil.getSmartCoinBackgroundColor(SmartShopperUtil.getSmartShopper().smartCoins!)
}
userName.text = SmartShopperUtil.getSmartShopperNameWithFullName(SmartShopperUtil.getSmartShopper().name)
coinIcon.text = AEVIcons.AEV_SMART_COIN
}
}
I have added a View in the ViewController I want to add this view, and I have set the custom class of this view as UserCoinView. After that, I have made a connection to the ViewController, and in this ViewController I have no idea what to do in order to display my custom UIView.
Thanks in advance for your help.
There is couple of ways you can do this.
Add as subview programmatically.
If you use autolayout, better place for that is viewDidLayoutSubviews method.
var myCustomView: UserCoinView? // declare variable inside your controller
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if myCustomView == nil { // make it only once
myCustomView = NSBundle.mainBundle().loadNibNamed("UserCoinView", owner: self, options: nil).first as? UserCoinView
myCustomView.frame = ...
self.view.addSubview(myCustomView) // you can omit 'self' here
// if your app support both Portrait and Landscape orientations
// you should add constraints here
}
}
Add as subview in InterfaceBuilder.
You simply need put an empty view to you controller inside the storyboard, and assign your class for this view in Identity Inspector. After that, you can drag-n-drop outlets to your controller classes if you need one.
As for me, I prefer the second method because you don't need to hardcode frame / create constraints programmatically, just add autolayout.
You can try This
override init(frame: CGRect) {
super.init(frame: frame)
loadViewFromNib ()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
loadViewFromNib ()
}
func loadViewFromNib() {
let view = UINib(nibName: "CreditCardExperyView", bundle: NSBundle(forClass: self.dynamicType)).instantiateWithOwner(self, options: nil)[0] as! UIView
view.frame = bounds
view.autoresizingMask = [.FlexibleWidth, .FlexibleHeight]
self.addSubview(view);
}
// Call subview
let creditCardView : CreditCardExperyView = CreditCardExperyView(frame: CGRect(x: 0, y: UIScreen.mainScreen().bounds.size.height - 280, width: UIScreen.mainScreen().bounds.size.width, height: 280))
selfView.addSubview(creditCardView)
Add it to a UIViewController's or UITableViewController's content view (or some other view in the view controller's view hierarchy) as a subview.
in Latest swift -> for example:
let headerView = Bundle.main.loadNibNamed("SectionHeaderView", owner:
self, options: nil)?.first as? SectionHeaderView
self.view.addSubview(headerView!)
headerView?.frame = CGRect(x:0, y: 0, width: view.frame.width, height: 50.0)
I'm pretty new myself, but this should work.
Adding:
viewControllerName.addSubview(userCoinView)
Removing:
userCoinView.removeFromSuperview()
You can also use generic function. For project use make it global
struct GenericFunctions {
static func addXIB<T>(xibName: String) -> T? {
return Bundle.main.loadNibNamed(xibName, owner: self, options: nil)?.first as? T
}
}
Use this generic function like:-
if let cell: StudentStatusTableViewCell = GenericFunctions.addXIB(xibName: "StudentStatusTableViewCell") {
return cell
} else {
return UITableViewCell()
}
Benefit of generic is you can use this function for adding View, Tableviewcell and any other element. make sure you are using type in call like let cell: StudentStatusTableViewCell otherwise compiler won't infer the type.
Happy coding :)
Related
I need to create custom xib uiview programatically
Here i dont want to add any view in owner viewcontroller to give its class name as ReusableContainerview and show that... because i have around 30 viewcontrollers so i cant add view in all my 30 viewcontrollers
i just want to show custom view in my homevc without adding uiview in storybard but how to call programatically in homevc to show custom view.. please guide me
code: code for creating custom view, to use in all my screens
import UIKit
class ReusableContainerview: UIView {
#IBOutlet weak var containerView: UIView!
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit(){
var viewFromXib = Bundle.main.loadNibNamed("ReusableContainerview", owner: self, options: nil)![0] as! UIView
viewFromXib.frame = self.bounds
viewFromXib = ReusableContainerview(frame: CGRect(x: 0, y: 0, width: bounds.width * 0.9, height: 300))
addSubview(viewFromXib)
}
}
for eg i would like to call custom view like this in my home page but in my home screen i am not getting reuseView.. how to show reuseView in home without adding uiview in storyboard.. please guide me
import UIKit
var reuseView: ReusableContainerview?
class HomeVC: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
reuseView?.isHidden = false
}
}
I have a custom view that I am trying to load from a custom XIB, but the view appears to be blank when loaded, even thought it has the correct sizes when debugged.
My debug statements show that the frame has the correct sizes:
commonInit()
XIB: MyCustomView
myView Frame: (0.0, 0.0, 320.0,568.0)
myView ContentSize: (320.0, 710.0)
This is my custom VC that I am using to call my Custom View
class MyCustomViewController: UIViewController {
var myView : MyCustomView!
override func viewDidLoad() {
super.viewDidLoad()
myView = MyCustomView(frame: self.view.frame)
self.view.addSubview(myView)
updateScrollViewSize()
print("myView Frame: \(myView.frame)")
print("myView ContentSize: \(myView.contentView.contentSize)")
}
func updateScrollViewSize () {
var contentRect = CGRect.zero
for view in myView.contentView.subviews {
contentRect = contentRect.union(view.frame)
}
myView.contentView.contentSize = CGSize(width: myView.contentView.frame.size.width, height: contentRect.size.height + 5)
}
}
There is a XIB that has the files owner as MyCustomView and all the outlets are hooked up correctly.
class MyCustomView: UIView {
let kCONTENT_XIB_NAME = "MyCustomView"
#IBOutlet var contentView: UIScrollView!
#IBOutlet weak var lbl_datein: UILabel!
//.. A bunch of other GUI elements for the scrollview
#IBOutlet weak var text_location: UITextField!
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
func commonInit() {
print(#function)
print("XIB: \(kCONTENT_XIB_NAME)")
Bundle.main.loadNibNamed(kCONTENT_XIB_NAME, owner: self, options: nil)
contentView.addSubview(self)
contentView.frame = self.bounds
contentView.backgroundColor = .blue
}
}
Does anyone see what I have done wrong when trying to load the view
I'm going to post an alternative to what you've done, using an extension.
extension UIView {
#discardableResult
func fromNib<T : UIView>(_ nibName: String? = nil) -> T? {
let bundle = Bundle(for: type(of: self))
guard let view = bundle.loadNibNamed(nibName ?? String(describing: type(of: self)), owner: self, options: nil)?[0] as? T else {
return nil
}
view.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(view)
view.autoPinEdgesToSuperviewEdges()
return view
}
}
*Note that I am using PureLayout for convenient autolayout management, you could just apply the constraints manually yourself though if you aren't using PureLayout.
Using the above all you have to do is call the below from your init;
fromNib()
*Final note. The custom view name must match the nib name, otherwise you must pass the nib name in to you fromNib function.
You now have something much more reusable.
If my alternative answer is too much, let me try solve your existing issue. Instead of the below;
Bundle.main.loadNibNamed(kCONTENT_XIB_NAME, owner: self, options: nil)
contentView.addSubview(self)
Try;
let nibView = Bundle.main.loadNibNamed(kCONTENT_XIB_NAME, owner: self, options: nil)
self.addSubView(nibView)
I couldn't get this to run copy/pasting the code. Maybe there's some setup missing, but I'm having a hard time understanding how it's supposed to work. The original code in the question crashes on this line:
contentView.addSubview(self)
because when you have IBOutlets, they will always be nil if you initialize it using MyCustomView(frame: self.view.frame). It has to call the initWithCoder function.
There's a lot going on here, but this is how I would do it:
class MyCustomViewController: UIViewController {
var myView: MyCustomView!
override func viewDidLoad() {
super.viewDidLoad()
myView = Bundle.main.loadNibNamed("MyCustomView", owner: self, options: nil)?.first as? MyCustomView
self.view.addSubview(myView)
updateScrollViewSize()
print("myView Frame: \(myView.frame)")
print("myView ContentSize: \(myView.contentView.contentSize)")
}
func updateScrollViewSize () {
var contentRect = CGRect.zero
for view in myView.contentView.subviews {
contentRect = contentRect.union(view.frame)
}
myView.contentView.contentSize = CGSize(width: myView.contentView.frame.size.width, height: contentRect.size.height + 5)
}
}
class MyCustomView: UIView {
let kCONTENT_XIB_NAME = "MyCustomView"
#IBOutlet var contentView: UIScrollView!
#IBOutlet weak var lbl_datein: UILabel!
//.. A bunch of other GUI elements for the scrollview
#IBOutlet weak var text_location: UITextField!
}
I'm assuming that the top-level object in the nib is of class MyCustomView, which is going to lead to a lot of weird things. loadNibNamed will call init?(coder aDecoder: NSCoder), so ideally you'd just be calling that from your view controller in the first place, instead of from the custom view object.
With regards to the "can't add self as subview" error, I did not see that error while running, but I would expect it from this line:
contentView.addSubview(self)
since that's exactly what it does, add self as a subview of a view that's already a subview of self.
I know how to use #IBDesignable with custom views.
but is it possible to use IBDesignable for cells and render them in storyboard?
for example: i have a collectionViewController in storyboard, and added a uiCollectionCell and specified class as my customCellClass.
p.s: i know for using Xibs in collecionViews and tableViews we have to call method registerNib:forReuseIdentifer in code (and i am doing it). just wondered, is it possible to see it's rendered view in storyboard or not.
p.s2: i found this and it works perfectly with UIViews, but don't know how to make it work with CollectionCells and TableCells. :(
Yes. Here is what I found with Xcode 10.1 and iOS 12. Adding #IBDesignable to the custom subclass of UICollectionViewCell did work intermittently, but this works more reliably:
Add #IBDesignable to a custom subclass of UIView
Override layoutSubviews(), and define the appearance there
Optionally, if you want to define dummy data for IB only, override prepareForInterfaceBuilder()
Add that custom view to your prototype UICollectionViewCell in Interface Builder
You should see Interface Builder "build" the views and draw your change in your customer view (I find this unreliable, too. If nothing happens and Menu / Editor / Automatically Refresh Views is checked, make some other change in Interface Builder)
Example Class
#IBDesignable
class Avatar: UIView {
// Despite not being used for views designed in Interface Builder, must still be defined for custom UIView subclasses with #IBDesignable, or IB will report errors
override init(frame: CGRect) {
super.init(frame: frame)
}
// Used when a view is designed inside a view controller scene in Interface Builder and assigned to this custom UIView subclass
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func layoutSubviews() {
super.layoutSubviews()
self.layer.cornerRadius = self.bounds.width / 2
self.backgroundColor = UIColor.gray
}
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
self.backgroundColor = UIColor.blue
}
}
Yep. Here's how I did it.
First make sure the File Owner of NIB file is set to your custom cell class. Check this
Override the prepareForInterfaceBuilder method and add the contentView from NIB file in the contentView of prototype cell. This is what it looks like.
// ArticleTableViewCell.swift
import UIKit
#IBDesignable
class ArticleTableViewCell: UITableViewCell {
#IBOutlet weak var authorLabel: UILabel!
#IBOutlet weak var authorImage: UIImageView!
#IBOutlet weak var titleLabel: UILabel!
#IBOutlet weak var dateCreatedLabel: UILabel!
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
addNIBContentView(toView: contentView)
}
private func addNIBContentView() {
let view = loadContentViewFromNib()
view.frame = bounds
view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
contentView.addSubview(view)
}
private func loadContentViewFromNib() -> UIView {
let bundle = Bundle(for: type(of: self))
// Make sure your NIB file is named the same as this class, or else
// Put the name of NIB file manually (without the file extension)
let nib = UINib(nibName: String(describing: type(of: self)), bundle: bundle)
let view = nib.instantiate(withOwner: self, options: nil).first as! UIView
return view
}
}
Now set the custom class of the prototype cell in your table view to the one above and refresh the views.
Editor > Refresh All Views
If it still does not show up. Just clear the build folder and refresh all views again.
Product > Clean Build Folder
I made a handy extension for myself to reuse the last 2 functions in all UITableViews/UICollectionViews
// ViewExtensions.swift
import UIKit
extension UIView {
func addNIBContentView(toView contentView: UIView? = nil) {
let view = loadContentViewFromNib()
// Use bounds not frame or it'll be offset
view.frame = bounds
// Make the view stretch with containing view
view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
if let contentView = contentView {
contentView.addSubview(view)
} else {
addSubview(view)
}
}
private func loadContentViewFromNib() -> UIView {
let bundle = Bundle(for: type(of: self))
// Make sure your NIB file is named the same as it's class
let nib = UINib(nibName: String(describing: type(of: self)), bundle: bundle)
let view = nib.instantiate(withOwner: self, options: nil).first as! UIView
return view
}
}
// ArticleTableViewCell.swift
import UIKit
#IBDesignable
class ArticleTableViewCell: UITableViewCell {
#IBOutlet weak var authorLabel: UILabel!
#IBOutlet weak var authorImage: UIImageView!
#IBOutlet weak var titleLabel: UILabel!
#IBOutlet weak var dateCreatedLabel: UILabel!
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
addNIBContentView(toView: contentView)
}
}
After lots of testing and working with the library I came up with this:
you should not add TableViewCell or CollectionViewCells inside .nib files, instead you have to add simple View. I'm not sure if it's gonna show up inside storyboard or not (haven't checked it yet) but it makes errors go away. Now you can even use autoLayout for self sizing cells.
I am trying to create a custom view in Swift by subclassing UIView, and I have a view board named MyViewPanel.xib that has its class assigned to MyCustomView. The implementation is as following:
import UIKit
#IBDesignable
class MyCustomView: UIView {
#IBOutlet weak var title: UILabel!
var question: Question {
didSet {
print("did set question, title is: \(question.title)")
}
}
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func drawRect(rect: CGRect) {
let height = rect.height
let width = rect.width
let color: UIColor = UIColor.whiteColor()
let drect = CGRect(x: (width * 0.25), y: (height * 0.25), width: (width * 0.5),height: (height * 0.5))
let bpath: UIBezierPath = UIBezierPath(rect: drect)
color.set()
bpath.stroke()
}
override func awakeFromNib() {
super.awakeFromNib()
print("awake from nib!")
self.title.text = "Test title" // error: found nil while unwrapping an Optional Value
}
}
During the run time, I encountered the following error:
fatal error: unexpectedly found nil while unwrapping an Optional value
Since the awakeFromNib() is the first lifecycle event in UIView, I do not understand why the title UILabel is nil in this case.
Edit:
To use this custom view, I just draw a UIView rectangle on my storyboard and assign its class to MyCustomView. In the viewDidLoad() method of my ViewController for the storyboard, I set the question on the custom view:
override func viewDidLoad() {
// myCustomView is an IBOutlet in the view controller
myCustomView.question = question
}
The code part looks OK but this would suggest the outlets have not been connected correctly in interface builder. In the outlets section in interface builder can you see the title outlet is connected and does not show an exclamation mark indicating an error. Also the IBOutlet line in the code should have a filled circle next to it to indicate the outlet is connected correctly and the class is correctly assigned from interface builder.
I figured out how to set the text for a UILabel in a convenience initializer for a UIView subclass I wrote:
#IBOutlet weak var messageLabel: UILabel!
init?(frame: CGRect, message: String) {
super.init(frame: frame)
guard let view = NSBundle.mainBundle().loadNibNamed("MyViewSubclass", owner: self, options: nil).first as? MyViewSubclass else {
return nil
}
// Without the following line, my view was always 600 x 600
view.frame = frame
self.addSubview(view)
guard let messageLabel = view.messageLabel else {
return
}
messageLabel.text = message
}
I'm trying to understand how to properly subclass view which is loaded from a xib in Swift.
I've got TitleDetailLabel class which is subclass of UIControl. This class has titleLabel and detailLabel outlets which are UILabels.
class TitleDetailLabel: UIControl {
#IBOutlet weak var titleLabel: UILabel!
#IBOutlet weak var detailLabel: UILabel!
override func awakeAfterUsingCoder(aDecoder: NSCoder) -> AnyObject? {
return NTHAwakeAfterUsingCoder(aDecoder, nibName: "TitleDetailLabel")
}
func setTitle(text: String) {
self.titleLabel.text = text
}
func setDetailText(text: String) {
self.detailLabel.text = text
}
}
XIB structure:
Placeholders
File's Owner: NSObject (not changed)
First Responder
Title Detail Label - UIView - TitleDetailLabel class
Label - UILabel - title label
Label - UILabel - detail label
In Storyboard I've got view controller and placeholder - simple UIView object with constraints.
I've created extension to UIView class to simplify swapping placeholder with object I am interested in. It works good with this TitleDetailLabel class. Here is how it looks:
extension UIView {
public func NTHAwakeAfterUsingCoder(aDecoder: NSCoder, nibName: String) -> AnyObject? {
if (self.subviews.count == 0) {
let nib = UINib(nibName: nibName, bundle: nil)
let loadedView = nib.instantiateWithOwner(nil, options: nil).first as UIView
/// set view as placeholder is set
loadedView.frame = self.frame
loadedView.autoresizingMask = self.autoresizingMask
loadedView.setTranslatesAutoresizingMaskIntoConstraints(self.translatesAutoresizingMaskIntoConstraints())
for constraint in self.constraints() as [NSLayoutConstraint] {
var firstItem = constraint.firstItem as UIView
if firstItem == self {
firstItem = loadedView
}
var secondItem = constraint.secondItem as UIView?
if secondItem != nil {
if secondItem! == self {
secondItem = loadedView
}
}
loadedView.addConstraint(NSLayoutConstraint(item: firstItem, attribute: constraint.firstAttribute, relatedBy: constraint.relation, toItem: secondItem, attribute: constraint.secondAttribute, multiplier: constraint.multiplier, constant: constraint.constant))
}
return loadedView
}
return self
}
}
I decided to create BasicTitleDetailLabel subclass of TitleDetailLabel class to keep there some configuration code and other stuff.
class BasicTitleDetailLabel: TitleDetailLabel {
override func awakeFromNib() {
super.awakeFromNib()
self.setup()
}
override init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.setup()
}
override init(frame: CGRect) {
super.init(frame: frame)
self.setup()
}
private func setup() {
self.titleLabel.textColor = UIColor.NTHCadetGrayColor()
self.detailLabel.textColor = UIColor.NTHLinkWaterColor()
}
}
But application crashes every time after I changed class of this placeholder from TitleDetailLabel to BasicTitleDetailLabel.
App crashes because titleLabel and detailLabel are nil.
How can I properly use this TitleDetailLabel class with xib and how to subclass this correctly? I don't want to create another xib which looks the same like the first one to use subclass.
Thanks in advance.
Make sure you make the set the File's Owner for the .xib file to the .swift file. Also add an outlet for the root view and then load the xib from code. This is how I did it for a similar project:
import UIKit
class ResuableCustomView: UIView {
#IBOutlet var view: UIView!
#IBOutlet weak var label: UILabel!
#IBAction func buttonTap(sender: UIButton) {
label.text = "Hi"
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
NSBundle.mainBundle().loadNibNamed("ReusableCustomView", owner: self, options: nil)[0] as! UIView
self.addSubview(view)
view.frame = self.bounds
}
}
It is a little different than yours but you can add the init:frame method. If you have an awakeFromNib method then don't load the setup method in both awakeFromNib and init:coder.
My full answer for the above code project is here or watch this video.