I work with Nibs. I have two screens that will use the "same" UIView component with the same behavior. It's not the same component because in each screen i placed a UIView and made the same configuration, as show on the image.
To solve this and prevent replicate the same code in other classes i wrote one class, that is a UIView subclass, with all the functions that i need.
After that i made my custom class as superclass of these UIView components to inherit the IBOutlets and all the functions.
My custom class is not defined in a Nib, is only a .swift class.
I made all the necessary connections but at run time the IBOutlets is Nil.
The code of my custom class:
class FeelingGuideView: UIView {
#IBOutlet weak var firstScreen: UILabel!
#IBOutlet weak var secondScreen: UILabel!
#IBOutlet weak var thirdScreen: UILabel!
#IBOutlet weak var fourthScreen: UILabel!
private var labelsToManage: [UILabel] = []
private var willShow: Int!
private var didShow: Int!
override init(frame: CGRect) {
super.init(frame: frame)
self.initLabelManagement()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.initLabelManagement()
}
private func initLabelManagement() {
self.initLabelVector()
self.willShow = 0
self.didShow = 0
self.setupLabelToShow(label: labelsToManage[0])
self.setupLabels()
}
private func initLabelVector() {
self.labelsToManage.append(self.firstScreen)
self.labelsToManage.append(self.secondScreen)
self.labelsToManage.append(self.thirdScreen)
self.labelsToManage.append(self.fourthScreen)
}
private func setupLabels() {
for label in labelsToManage {
label.layer.borderWidth = 2.0
label.layer.borderColor = UIColor(hex: "1A8BFB").cgColor
}
}
func willShowFeelCell(at index: Int) {
self.willShow = index
if willShow > didShow {
self.setupLabelToShow(label: labelsToManage[willShow])
}
else if willShow < didShow {
for i in didShow ... willShow + 1 {
let label = labelsToManage[i]
self.setupLabelToHide(label: label)
}
}
}
private func setupLabelToShow(label: UILabel) {
label.textColor = UIColor.white
label.backgroundColor = UIColor(hex: "1A8BFB")
}
private func setupLabelToHide(label: UILabel) {
label.textColor = UIColor(hex: "1A8BFB")
label.backgroundColor = UIColor.white
}
}
I found this question similar to mine: Custom UIView from nib inside another UIViewController's nib - IBOutlets are nil
But my UIView is not in a nib.
EDIT:
I overrided the awakeFromNib but it neither enter the method.
More explanation:
My custom class is only superClass of this component:
Which i replicate on two screens.
One screen is a UITableViewCell and the another a UIViewController.
It's all about to manage the behavior of the labels depending on the screen that is showing at the moment on the UICollectionView
When the initLabelVector() function is called at the required init?(coder aDecoder:) it arrises a unwrap error:
The error when try to open the View:
Cannot show the error with the UITableViewCell because it is called at the beginning of the app and don't appear nothing. To show the error with the screen i needed to remove the call of the UITableViewCell.
The UITableViewCell is registered first with the tableView.register(nib:) and after using the tableView.dequeueReusebleCell.
The UIViewController is called from a menu class that way:
startNavigation = UINavigationController(rootViewController: FellingScreenViewController())
appDelegate.centerContainer?.setCenterView(startNavigation, withCloseAnimation: true, completion: nil)
The problem is this code:
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.initLabelManagement()
}
The trouble is that init(coder:) is too soon to speak of the view's outlets, which is what initLabelManagement does; the outlets are not hooked up yet. Put this instead:
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func awakeFromNib() {
super.awakeFromNib()
self.initLabelManagement()
}
How I arrived at this answer:
As a test, I tried this:
class MyView : UIView {
#IBOutlet var mySubview : UIView!
required init?(coder aDecoder: NSCoder) {
super.init(coder:aDecoder)
print(#function, self.mySubview)
}
override func awakeFromNib() {
super.awakeFromNib()
print(#function, self.mySubview)
}
}
Here's the output:
init(coder:) nil
awakeFromNib() <UIView: 0x7fc17ef05b70 ... >
What this proves:
init(coder:) is too soon; the outlet is not hooked up yet
awakeFromNib is not too soon; the outlet is hooked up
awakeFromNib is called, despite your claim to the contrary
When the init() from my custom class is called the IBOutlets are not hooked up yet.
So, I created a reference on the parent view and called the iniLabelManagement() from the viewDidLoad() method and everything worked.
Thank you matt, for the help and patience!
Related
I'm building a keyboard extension.
In my program I have a view controller(1) and a view(2) class which I use for a xib file .
1)
class KeyboardViewController: UIInputViewController {
override func viewDidLoad() {
super.viewDidLoad()
let nib = UINib(nibName: "view1", bundle: nil)
let objects = nib.instantiate(withOwner: self, options: nil)
view = objects[0] as? UIView
}
class View1: UIView {
#IBOutlet weak var someLabel: UILabel!
#IBOutlet weak var someButton: UIButton!
}
I wanted to instantiate the view class inside of my view controller so, following apple's documentation I did this:
class KeyboardViewController: UIInputViewController {
let v1 = View1()
let v2 = View2()
}
The problem is that whenever I try to call inside of my view did load something like:
v1.someLabel.text = "something"
and I run my app, for some reasons it doesn't work and eventually crashes.
Important things: the views are connected to two different .xib files and I'm working on a custom keyboard extension.
I'm sure I'm missing something in the instantiation but I can't find out what it is, I see other developers on git hub doing exactly the same as I do but running their apps gives no problem... So what am I missing out? If you can please send me more documentation about it as well...
Edit:
In both my view classes I'm doing the following to initialize them:
class View1: UIView {
#IBOutlet weak var someLabel: UILabel!
#IBOutlet weak var someButton: UIButton!
init(label: UILabel, button: UIButton) {
self.someLabel = label
self.someButton = button
super.init(frame: CGRect(x: 0, y: 0, width: 330, height: 200))
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
But then in my main view controller, when i call let v1 = View1(), it becomes:
let v1 = View1(coder: NSCoder)
And I'm having a hard time figuring out what to put in the parameter field
If it's crash, I think that your UITextField in your View1 is null. This field is init in your class or it refers to a UINib or UIStoryboard ?
To fix this, try init child elements in your custom UIView as well.
I found a solution by myself:
Go to your xib file, in the file's owner section set the custom class on View1; As per the view section, set the custom class to UIView;
In your class View1: UIView add a view outlet from your xib file ie:
#IBOutlet var view: UIView!
Paste this in your class View1: UIView
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
private func commonInit(){
Bundle.main.loadNibNamed("view1", owner: self, options: nil)
addSubview(view)
view.frame = self.bounds
view.autoresizingMask = [.flexibleHeight, .flexibleWidth]
//Add any other setup here
}
Now create another xib file, make sure it's file's Owner is KeyboardViewController. Add an UIView to it and make sure its class is of type View1.
Now go to class KeyboardViewController: UIInputViewController and link the view of type View1 in your code. Right under it write weak var v : View1!
In viewDidLoad()you can eventually write view = View1()
You're done!
Now I'm practicing build IOS app without using storyboard , but I have a problem want to solve , I created a custom UIView called BannerView and added a background(UIView) and a title(UILabel) , and called this BannerView in the MainVC , but run this app , it crashes at the function setupSubviews() and I don't know why.
import UIKit
import SnapKit
class BannerView: UIView {
var background: UIView!
var title: UILabel!
override init(frame: CGRect) {
super.init(frame: frame)
setupSubviews()
}
convenience init() {
self.init(frame: CGRect.zero)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupSubviews()
}
func setupSubviews() {
background.backgroundColor = .gray
title.text = "INHEART"
self.addSubview(background)
self.addSubview(title)
}
override func layoutSubviews() {
background.snp.makeConstraints { make in
make.width.equalTo(ScreenWidth)
make.height.equalTo(BannerHeight)
make.left.top.right.equalTo(0)
}
title.snp.makeConstraints { make in
make.width.equalTo(100)
make.center.equalTo(background.snp.center)
}
}
}
class MainVC: UIViewController {
var bannerView:BannerView!
override func viewDidLoad() {
super.viewDidLoad()
bannerView = BannerView(frame: CGRect.zero)
view.addSubview(bannerView)
}
}
Your properties do not appear to be initialised
var background: UIView!
var title: UILabel!
You should initialize these in your init method
self.background = UIView()
self.title = UILabel()
If you use force unwrapping on a class property you must initialize in the init method. XCode should be complaining to you about this and your error message should show a similar error
You are not initialised the background view please initialised them
self.background = UIView()
self.title = UILabel()
and if you want to create custom view by use of xib the follow them Custum View
You must have to initialised the self.background = UIView() and self.title = UILabel() first.
You can initalised them in setupSubviews() function before the set/assign values to them.
I've recently dived into the world of Swift 3 and Xcode 8 and have been working on a simple game to learn as much as possible but I have run into an issue where my custom class is crashing when I segue into it because the IBOutlets which I dragged into it are nil.
I am using StoryBoard and AutoLayout and have dragged several fields into my class statistics:UIScrollView from the storyboard.
class StatisticsView: UIScrollView, sendLabelDelegate {
#IBOutlet weak var GamesWonNum: UILabel!
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.GamesWonNum = aDecoder.decodeObject(forKey: "GamesWonNum") as! UILabel!
}
override func encode(with aCoder: NSCoder) {
aCoder.encode(self.GamesWonNum, forKey: "GamesWonNum")
}
func setLabel(data: String) {
let temp = Int(data)
setNumber(num: temp!)
}
override func awakeFromNib() { }
override init(frame: CGRect) {
super.init(frame: frame)
}
func setNumber(num: Int){
GamesWonNum.text = String(num)
}
}
I have made sure that the statistics class is set as the identifier for the storyboard scrollview. I've done a lot of research into the topic, and I couldn't find a concrete answer on exactly how this works. I found in another post that calling the function awakefromNib() will fix the issue and it has, but I want to understand what awakefromNib() does and why I cannot simply init() the view.
My view Controller hierarchy is as follows:
MainMenuVC | --Segue--> SettingsVC
MainMenyVC | --Segue--> StatisticsVC
class StatisticsVC: UIViewController, sendLabelDelegate{
#IBOutlet weak var StatsScrollView: UIScrollView!
var settingsLabel:String = ""
override func viewDidLoad() {
super.viewDidLoad()
if UIScreen.main.bounds.height < 600 {
StatsScrollView.contentSize.height = 700
print("screensizeif: \(UIScreen.main.bounds.height), \(StatsScrollView.contentSize.height)")
}else{
StatsScrollView.contentSize.height = UIScreen.main.bounds.height
print("screensizeelse: \(UIScreen.main.bounds.height), \(StatsScrollView.contentSize.height)")
}
StatsScrollView.contentSize.width = UIScreen.main.bounds.width
StatsScrollView.isUserInteractionEnabled = true
}
func setLabel(data: String) {
settingsLabel = data
}
override var shouldAutorotate: Bool {
return true
}
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
if UIDevice.current.userInterfaceIdiom == .phone {
return .allButUpsideDown
} else {
return .all
}
}
}
My understanding is that the viewController will init all of its views before going into the viewdidLoad() function, so why is awakefromNib() necessary and what exactly should I define inside of it. Further, why can't I initialize the class inside init()? If I try to set the GamesWon Outlet inside init(), the outlet is nil.
Its a bit difficult for me to wrap my head around the relationship that exists here between a view controller, the custom scrollview I have, and the outlets being nil. If anyone could clarify I would very much appreciate it.
Thank you!
I'm using DZNEmptySet to load when there's nothing to display in a UITableView. It works fine for DZNEmptySet's methods like titleForEmptyDataSet, imageForEmptyDataSet, but not the one I want to use (which is customViewForEmptyDataSet).
When I try to load the xib into the scrollView.frame, Xcode's memory starts bloating in 30 megabyte increments and the app hangs. I know I'm at fault, but I don't know what I'm mucking up.
I've looked at many answers here and tutorials on other sites, but I can't find a solution that works for this circumstance (which I think is pretty simple). Any feedback on that front would be greatly appreciated.
Here's customViewForEmptyDataSet on MyViewController
// App hangs on this and memory bloats in 30 megabyte increments.
func customViewForEmptyDataSet(scrollView: UIScrollView!) -> UIView! {
return EmptySetView(frame: scrollView.frame)
}
Here's the class for my EmptySetView that I'm trying to initialize:
import UIKit
class EmptySetView: UIView {
var view: UIView!
// These are connected to a xib
#IBOutlet weak var backgroundImageView: UIImageView!
#IBOutlet weak var viewLabel: UILabel!
#IBOutlet weak var viewTextView: UITextView!
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 = [.FlexibleWidth, .FlexibleHeight]
self.addSubview(self.view)
}
func loadViewFromNib() -> UIView {
let bundle = NSBundle(forClass:self.dynamicType)
let nib = UINib(nibName: "EmptySetView", bundle: bundle)
let view = nib.instantiateWithOwner(self, options: nil)[0] as! UIView
return view
}
}
Update
At Matt's suggestion, I investigated recursion I was experiencing and discovered source of the problem.
You do not specify the UIView class on the View for your xib. Instead, you click the yellow cube titled File's Owner and specify the UIView there, leaving the class field empty on the View in the Document Outline panel.
After cleaning caches and rebuilding, I can get the view to load in the hierarchy with this code called on MyViewController:
func customViewForEmptyDataSet(scrollView: UIScrollView!) -> UIView! {
let emptySetView = EmptySetView(frame: scrollView.frame)
return emptySetView
}
The EmptySetView xib and MyViewController don't know about each other until the xib is loaded, so you'll need to deal with layout constraints.
My guess is that in the nib you are loading the top level view is itself an EmptySetView. This is causing a recursion. You start by instantiating the EmptySetView in code:
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
In setup(), you load the nib. But this causes an EmptySetView to be instantiated again, from the nib. This time, the other initializer is called:
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
But setup() loads the nib, so we are now going around in circles, trying to nest an infinite number of nib-loaded views inside one another like matrushka dolls.
This is a near duplicate of this other SO question, however I'm running into some issues that it makes it appear like I'm not doing it correctly. For reference, I followed this excellent tutorial on creating re-usable views from a custom class and xib file (it's only a few minutes :) and I have no problems at all dragging that into another view onto my storyboard (as demonstrated at the end of the video)
Nevertheless for my case — I'm trying to call my custom class programmatically, and add it as a subview to one of my ScrollView instances.
class MainController: UIViewController {
#IBOutlet weak var scrollView: UIScrollView!
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
first.directionalLockEnabled = true
first.pagingEnabled = true
var item = MyCustomView(frame: CGRectMake(0, 0, self.view.frame.width, 200))
self.scrollView.addSubview(item)
}
}
My Custom view looks like this:
import UIKit
class MyCustomView: UIView {
#IBOutlet var view: UIView!
#IBOutlet weak var imageView: UIImageView!
#IBOutlet weak var dressTitle: UILabel!
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
NSBundle.mainBundle().loadNibNamed("MyCustomView", owner: self, options: nil)
self.view.frame = bounds
self.addSubview(self.view)
}
override init(frame: CGRect) {
super.init(frame: frame)
}
}
There is an associated .xib file with it too that has the label and image.
So my question is, my view never appears in my ScrollView. I've double checked the constraints on my scroll view... I can even append simple UIView's with obvious visible dimensions CGRectMake(0, 0, 100, 100)... and nothing ever gives. What am I missing?
With some messing around I accidentally got it to work by duplicating the loadFromNib method to a second initializer in the CustomView.swift file. As seen in the video tutorial posted in the original question it shows just one of the initializers.... but in my case I had to add extra code to the init(frame). Eg:
// once with a NSCoder
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
NSBundle.mainBundle().loadNibNamed("Item", owner: self, options: nil)
self.view.frame = bounds
self.addSubview(self.view)
}
// for this to work programmatically I had to do the same...
override init(frame: CGRect) {
super.init(frame: frame)
NSBundle.mainBundle().loadNibNamed("Item", owner: self, options: nil)
self.view.frame = bounds
self.addSubview(self.view)
}