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.
Related
I want to add a custom view in TableViewHeader. But when I run the following code it creates a Cycle and app stuck for any user interaction.
import UIKit
class ExpandableView: UIView {
#IBOutlet weak var userImgView: UIImageView!
#IBOutlet weak var userNamelbl: UILabel!
#IBOutlet weak var countLbl: UILabel!
#IBOutlet weak var rightArrowImgView: UIImageView!
var isExpanded = false
var contentView: UIView!
#IBOutlet weak var checkMarkView: UIImageView!
// Only override draw() if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
override func draw(_ rect: CGRect) {
// Drawing code
}
public override init(frame: CGRect) {
super.init(frame: frame)
setUpView()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setUpView()
}
private func setUpView() {
let bundle = Bundle(for: type(of: self))
let nib = UINib(nibName: "ExpandableView", bundle: bundle)
self.contentView = nib.instantiate(withOwner: self, options: nil).first as? UIView
addSubview(contentView)
contentView.center = self.center
contentView.autoresizingMask = []
contentView.translatesAutoresizingMaskIntoConstraints = true
}
}
I am using it as follows:
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let frame = CGRect(x: 0, y: 0, width: tableView.frame.size.width, height: 60)
let expandabelView = ExpandableView(frame: frame)
return expandabelView
}
And it shows following error on run.
There may be many other ways, but I recommend you to make your ExpandableView reusable to improve performance.
First of all, simplify your ExpandableView class:
import UIKit
class ExpandableView: UITableViewHeaderFooterView {
#IBOutlet weak var userImgView: UIImageView!
#IBOutlet weak var userNamelbl: UILabel!
#IBOutlet weak var countLbl: UILabel!
#IBOutlet weak var rightArrowImgView: UIImageView!
var isExpanded = false
#IBOutlet weak var checkMarkView: UIImageView!
}
Please do not miss that the superclass is UITableViewHeaderFooterView, not UIView.
Second, check the settings of your ExpandableView.xib:
The Custom View setting of the defined view needs to be ExpandableView.
When you cannot choose ExpandableView from the pull down list, you may need to input manually. Do not forget to check Inherit Module From Target.
The Custom View setting of the File's Owner needs to be empty.
If there's some class already set, remove it manually.
Confirm all Outlets are connected properly to your ExpandableView.
(You should better reconnect them all, after you modified your xib.)
You may need to re-structure your view hierarchy and/or constraints.
Third, modify your view controller holding the table view.
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
tableView.delegate = self
tableView.dataSource = self
//...
let nib = UINib(nibName: "ExpandableView", bundle: nil)
tableView.register(nib, forHeaderFooterViewReuseIdentifier: "ExpandableView")
tableView.estimatedSectionHeaderHeight = 60
tableView.sectionHeaderHeight = UITableView.automaticDimension
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let expandabelView = tableView.dequeueReusableHeaderFooterView(withIdentifier: "ExpandableView")
// Frame size should be represented with constraints.
return expandabelView
}
When you create custom UIView, it should follow this if you want to use it init with frame.
class CustomView: UIView {
//This should be contentview of your xib
#IBOutlet var view: UIView!
let nibName = "CustomView"
override init(frame: CGRect) {
super.init(frame: frame)
xibSetup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
xibSetup()
}
private func xibSetup()
{
Bundle.main.loadNibNamed(nibName, owner: self, options: nil)
self.view.autoresizesSubviews = true
self.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(self.view)
self.view.frame = self.frame
}
}
Since you need to load view from nib, just load it and then add subview to your view. Then set content view's frame and set autoresizing mask of content view correctly
private func setUpView() {
Bundle.main.loadNibNamed("ExpandableView", owner: self, options: nil)
addSubview(contentView)
contentView.frame = bounds
contentView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
}
What I've done is:
1) Created a .xib file TranslationInfoWindow.xib:
2) Created TranslationInfoWindow.swift file with the follow content:
import UIKit
class TranslationInfoWindow: UIView {
// MARK: - Initializers
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupView()
}
// MARK: - Private Helper Methods
// Performs the initial setup.
private func setupView() {
let view = viewFromNibForClass()
view.frame = bounds
// Auto-layout stuff.
view.autoresizingMask = [
UIViewAutoresizing.flexibleWidth,
UIViewAutoresizing.flexibleHeight
]
// Show the view.
addSubview(view)
}
// Loads a XIB file into a view and returns this view.
private func viewFromNibForClass() -> UIView {
let bundle = Bundle(for: type(of: self))
let nib = UINib(nibName: String(describing: type(of: self)), bundle: bundle)
let view = nib.instantiate(withOwner: self, options: nil).first as! UIView
return view
}
#IBOutlet weak var avatarImageView: RoundedImageView!
#IBOutlet weak var titleLabel: UILabel!
#IBOutlet weak var usersLabel: UILabel!
}
3) Here I try to initialise my custom view:
func mapView(_ mapView: GMSMapView, markerInfoWindow marker: GMSMarker) -> UIView? {
// FIXME: There is a UIView but it doesn't want to be casted in TranslationInfoWindow
if let infoWindow = Bundle.main.loadNibNamed(
"TranslationInfoWindow", owner: view, options: nil)?.first as? TranslationInfoWindow {
return infoWindow
} else {
return nil
}
}
Now if I try to run the project I have the following error:
What am I doing wrong?
UPDATE:
Here's the hierarchy of xib:
In Interface Builder, did you change the class name in the Identity Inspector (3rd from left) tab from UIView to your custom class name?
You should set the correct class of TranslationInfoWindow.xib to be type TranslationInfoWindow in IB .
I have created a custom UIView in xib with class InfoPopUpView. I add it to my viewController. It works well and the Custom Class xib is set in the File's Owner.
I can set the titleLbl variable in my first viewcontroller no problem.
My problem appears when I in another viewcontroller want to use the InfoPopUpView again but with a different title...
The title does not change/update. It is as if InfoPopUpView remembers the last title I set and dont care for changing it..
How can I initialise? the titleLbl variable in the InfoPopUpView Class so that I can change it later?
Any help is very much appreciated - thank you !
ViewController
var popUpView: InfoPopUpView!
popUpView = InfoPopUpView(frame: CGRectMake(0, 0, 300, 268))
popUpView.titleLbl.text = "MyFirstTitle"
view.addSubview(popUpView)
Custom Class
class InfoPopUpView: UIView {
var view: UIView!
#IBOutlet weak var okBtn: UIButton!
#IBOutlet weak var titleLbl: UILabel!
#IBOutlet weak var textView: UITextView!
#IBAction func didTapOkBtn(sender: AnyObject) {
NSNotificationCenter.defaultCenter().postNotificationName("popUpController", object: nil)
}
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
func setup() {
view = loadViewFromNib()
view.frame = bounds
view.autoresizingMask = [UIViewAutoresizing.FlexibleWidth, UIViewAutoresizing.FlexibleHeight]
addSubview(view)
}
func loadViewFromNib() -> UIView {
let bundle = NSBundle(forClass: self.dynamicType)
let nib = UINib(nibName: "InfoPopUpView", bundle: bundle)
let view = nib.instantiateWithOwner(self, options: nil)[0] as! UIView
return view
}
}
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 :)
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.