IBOutlet is nil in IBDesignable class - swift

I have a custom view class that is supposed to just contain a label. I have an extension in each target that will provide the text that should be displayed in the view, this way I don't have recreate each controller just to have a different label and background color in it.
My custom code is:
#IBDesignable
class HomeAccentView: UIView {
#IBOutlet weak var nameLabel: UILabel!
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
override func awakeFromNib() {
super.awakeFromNib()
setup()
}
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
setup()
}
private func setup() {
backgroundColor = UIColor.main
nameLabel.text = String.headerText
nameLabel.textColor = UIColor.white
}
}
I have my nib's file owner as HomeAccentView and the label is connected as an outlet on the storyboard.
But whenever I try to set the text of the label it's always nil. What am I missing or doing wrong?

You did not set class for view. You set only file owner, which means that you told that HomeAccessView is responsible for loading the view, but your code is not showing this. Is there any motive behind this? Otherwise, remove class from file owner and set it for view.

Related

UIView IBOutlet not changing when change called from a different class

I have an XIB with a UILabel to it. I have referenced the UILabel to a UIView class that I have created. I can change the label using label.text = "hi" when initializing the view. When I try and call a change from another class it doesn't change the UILabel on screen (but if I print label.text it shows as what I set it to). I cannot make the UILabel load the text when initializing as the text could be changed by the user at any time. (switchText() is called from a UITableCell)
2nd class
class Second {
func switchText() {
let first = First()
DispatchQueue.main.async {
first.label.text = "bye"
}
}
}
1st class
class First: UIView {
let kCONTENT_XIB_NAME = "First"
#IBOutlet var label: UILabel!
#IBOutlet var contentView: UIView!
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
func commonInit() {
Bundle.main.loadNibNamed(kCONTENT_XIB_NAME, owner: self, options: nil)
contentView.fixInView(self)
}
override func awakeFromNib() {
super.awakeFromNib()
}
}
Also, in my XIB I have my UIView hooked up to File's Owner and contentView inside my UIView class. My label outlet goes to file's owner and then to the UIView class where it is declared as label.
You are not really changing the text on your First() class. What your switchText() function does is create another reference of the class named First and then set the text of the label for that new reference.
let first = First()
DispatchQueue.main.async {
first.label.text = "bye"
}
What you can do is make your switchText() function conform to a protocol then call it on your First() class through a delegate.
protocol SecondClassDelegate {
func didSwitchText(editedText: String)
}
class Second {
var delegate: SecondClassDelegate!
func switchText() {
delegate.didSwitchText("bye")
}
}
Now you can add this to your First() class
class First: SecondClassDelegate {
func didSwitchText(editedText: String) {
label.text = editedText
}
}
Just don't forget to set the delegate wherever you're setting your Second() class
let second = Second()
second.delegate = self
I suggest reading about this for a better understanding of delegates. https://www.appcoda.com/swift-delegate/

Change IBOutlet button's type

I'm trying to achieve something I'm not sure if it's doable. I have a xib for UITableViewCell that has a UIButton in it with custom class assigned in storyboard i.e MainButtonSuperClass. In UITableViewCell.swift outlet is connected like following,
#IBOutlet weak var button: MainButtonSuperClass!
Many different controllers are configuring this TableViewCell but button's UI will be different for all those controllers as defined in subclass i.e.
final class MainButtonSubClass: MainButtonSuperClass {
override func configure(){//different style of button }
}
How can I achieve this? so far I have tried following,
let button: MainButtonSuperClass = MainButtonSubClass()
button.configure()
cell.button = button
But this doesn't pick the style
EDIT:
//tableview cell
class TableViewCell: UITableViewCell {
#IBOutlet weak var button: MainButtonSuperClass!
}
//Controller A wants default style of superclass (MainButtonSuperClass)
func configureButton(buttonCell: TableViewCell) {
buttonCell.button.configure() //works
}
//Controller B wants different style of subclass (MainButtonSubClass)
func configureButton(buttonCell: TableViewCell) {
let button: MainButtonSuperClass = MainButtonSubClass()
button.configure()
cell.button = button // doesn't work or adopts the style
}
//Custom button's classes
final class MainButtonSuperClass: UIButton {
override func configure(){//styled button }
}
final class MainButtonSubClass: MainButtonSuperClass {
override func configure(){//different style of button }
}
Ok understand, let's try this
class CustomClass: UIButton {
class Type1Class: UIButton {
// Do all the setup here
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class Type2Class: UIButton {
// Do all the setup here
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
}
Then you can initialize it with the following:
#IBOutlet weak var button: CustomClass.Type1Class!
Hope it helps!
I found the solution. I will not use IBOutlet but just a property of UIButton that I will change dynamically with factory method. With IBOutlet it's not possible.

Subclass of UIView doesn't invoke didSet

I have a subclass of UIView named BaseView. In subclass of BaseView I create didSet with some code. In UIViewController I init this subclass of BaseView and he doesn't invoke his didSet
BaseView code:
class BaseView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
setupViews()
}
func setupViews() { }
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Subclass of BaseView code:
class DetailProductView: BaseView {
var product: Product? {
didSet {
productImage.image = UIImage(named: (product?.productImageName)!)
productTitle.text = product?.title
productCompositionLabel.text = product?.description
productPriceLabel.text = "₽" + product!.productPrice!.stringValue
productWeightLabel.text = product!.productWeight!.stringValue + "г."
}
}
UIViewController code:
class DetailProductController: UIViewController {
var product: Product?
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
let productView = DetailProductView(frame: self.view.bounds)
view.addSubview(productView)
view.layoutSubviews()
}
}
Everything is correct. You created instance of DetailProductView, but you never set any value to it’s product property. Thus didSet was never called (cause you didn’t set anything).
If you want it to be called you should set any value to this property.

Is there a way to make an Xcode template that generates a View-Controller pair?

I often use this pattern when creating UIViewController/UIView pairs. It would be nice if I could define a template in Xcode so that I could click New File -> [template] and generate a MyViewController.swift and MyView.swift like in the example below.
MyViewController.swift
class MyViewController: UIViewController {
override func loadView() {
self.view = MyView()
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
}
extension MyViewController : MyViewDelegate
{
// Provide data, pop off a navigation stack, etc
}
MyView.swift
protocol MyViewDelegate : class {
}
class MyView: UIView
{
weak var delegate : MyViewDelegate?
// MARK: - Initialization
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
func setup()
{
// Configure views
// Assemble
}
}

Where to set UiTextField delegate method in custom UiView

Error occurs when I set UITextField delegate.
My code is:
import UIKit
class UserAlertVC: UIView , UITextFieldDelegate {
/*
// Only override draw() if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
override func draw(_ rect: CGRect) {
// Drawing code
}
*/
override init(frame: CGRect) {
super.init(frame: frame)
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
self.addBehavior()
}
func addBehavior (){
print("Add all the behavior here")
userNameTxtField.delegate = self
passwordTxtField.delegate = self
}
func textFieldShouldReturn(textField: UITextField) -> Bool {
return true
}
func textFieldDidBeginEditing(textField: UITextField) {
}
#available(tvOS 10.0, *)
func textFieldDidEndEditing(textField: UITextField, reason: UITextFieldDidEndEditingReason) {
}
#IBAction func actionOnCancel(sender: UIButton) {
self .removeFromSuperview()
}
#IBAction func actionOnProceed(sender: UIButton) {
self .removeFromSuperview()
UserAlertVC.showAlertForUser()
}
#IBOutlet var userNameTxtField: UITextField!
#IBOutlet var passwordTxtField: UITextField!
static func showAlertForUser() {
let alert = NSBundle.mainBundle().loadNibNamed("KeyboardViewController", owner: self, options: nil)!.last as! UIView
let windows = UIApplication.sharedApplication().windows
let lastWindow = windows.last
alert.frame = UIScreen.mainScreen().bounds
lastWindow?.addSubview(alert)
}
}
Error message is:
fatal error: unexpectedly found nil while unwrapping an Optional value
I have used Custom Alert View using XIB.pls suggest any solution.
Firstly take a look at life cycle of the view. Depending on this it is possible to highlight that method awakeFromNib is quite suitable because:
The nib-loading infrastructure sends an awakeFromNib message to each
object recreated from a nib archive, but only after all the objects in
the archive have been loaded and initialized. When an object receives
an awakeFromNib message, it is guaranteed to have all its outlet and
action connections already established.
Make sure to put an #IBOutlet for the .Xib content view, also you need to add the Nib code. Last, make sure in your ViewController you set your UIView Outlet to be UserAlertVC and you add the awakeFromNib method. Please find attached the code. Let me know if you need further help.
Here is the code related to the .xib file.
import UIKit
class UserAlertVC: UIView, UITextFieldDelegate {
//MARK: - Outlets
#IBOutlet var contentView: UIView!
#IBOutlet var userNameTxtField: UITextField!
#IBOutlet var passwordTxtField: UITextField!
//MARK: - Loads
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
commonInit()
}
//MARK: - Functions
func commonInit() {
Bundle.main.loadNibNamed("UserAlertVC", owner: self, options: nil)
userNameTxtField.delegate = self
passwordTxtField.delegate = self
contentView.translatesAutoresizingMaskIntoConstraints = false
addSubview(contentView)
// add constraints programmatically
}
// add the rest of your code
}
Here is the code related to the ViewController.
class ViewController: UIViewController {
//MARK: - Outlets
#IBOutlet weak var userAlertVC: UserAlertVC!
//MARK: - Loads
override func viewDidLoad() {
super.viewDidLoad()
}
override func awakeFromNib() {
super.awakeFromNib()
}
}