How to separate view from viewcontroller using storyboard swift? - swift

Practicing MVC pattern and want to separate view from viewcontroller using storyboard.
in main.storyboard I have a viewcontroller, and there are some uilabels in rootview.
to separate view code from viewcontroller, I selected view from viewcontroller scene , created FruitDetailView class and subclassed it in storyboard identity inspector.
And connected UILabels to FruitDetailView class.
class FruitDetailView: UIView {
#IBOutlet weak var name: UILabel!
#IBOutlet weak var type: UILabel!
override init(frame: CGRect) {
super.init(frame: frame)
name.text = ""
type.text = ""
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func update(_ detail: Fruit) {
name.text = detail.name
type.text = detail.type
}
}
In the viewController , created FruitDetailView() instance.
And in loadview() methods, assigned fruitDetailView instance to view property.
class FruitDetailViewController: UIViewController {
private let fruitDetailView = FruitDetailViewController()
var fruit = Fruit()
override func loadView() {
view = fruitDetailView
fruitDetailView.update(fruit)
}
}
But when I run the app, app crashes with error.
How can I fix this?

Your problem is you want to reach your FruitDetail class created before even its properties being created. Put a breakpoint to the name.text = "" and run and you will see what i mean. To solve this just control your nil value safely :
if let label = name { // by the way consider in future to assign values like nameLabel rather than name
name.text = "put your string value"
}

Related

Using custom uiview in viewcontroller

I'm trying to use a custom uiview that is in a xib file. I'm a beginner and don't know where to start. I have this so far, but I don't know what I should set my placeholder view equal to
edit: full viewController:
import UIKit
class Viewcontroller: UIViewController {
#IBOutlet weak var placeholderView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
placeholderView = view
}
class func instanceFromNib() -> CustomView {
let view = UINib(nibName: "CustomView", bundle: nil).instantiate(withOwner: nil, options: nil)[0] as! CustomView
return view
}
A. Let me suppose that you have a xib file titled 'TestView.' Also let me suppose that you have a subclass file of UIView titled 'TestView.'
B. Select your xib file. And select File's Owner. And set the class to TestView (the name of the subclass file).
C. Open the xib file with Interface Builder. Select 'Custom View.' (in the middle pane) IBOutlet-Connect this view to your UIView swift file. For example, name it like the following. #IBOutlet var customView: NSView!
D. This subclass file should have the following.
import UIKit
class TestView: UIView {
#IBOutlet var contentView: 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("TestView", owner: self, options: nil)
addSubview(contentView)
contentView.frame = self.bounds
}
}
E. Open the storyboard. Drag and drop a Custom View onto the view controller scene. Under the identity inspect, set the class name to TestView.
F. IBOutlet-connect the custom view object in the view controller like the following.
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var testView: TestView!
override func viewDidLoad() {
super.viewDidLoad()
}
}
That's all.

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/

Is it possible to modify the properties of a subclass from a parent class in Swift 4?

Via a method or closure, perhaps?
I created a subclass view controller of my superclass/parent view controller and placed labels with placeholder text in that subclass view controller.
I want to set the labels' values to blank strings from the superclass/parent view controller, or, specifically, from an IBAction function that causes the subclass view controller to appear.
Here is the code, first from the parent class, then from the subclass...
'''
class ViewController: UIViewController {
#IBAction func leavingView(){
self.EntryViewController.entryDateLabel.text = ""
self.EntryViewController.entryLabel.text = ""
self.dismiss(animated: true, completion: nil)
}
'''
then from the subclass...
'''
class EntryViewController: ViewController {
#IBOutlet var entryDateLabel: UILabel!
#IBOutlet var entryLabel: UILabel!
}
'''
I have come up with 2 solutions to this problem, without having the parent view controller know about its subclass.
In the first example the parent sets properties on itself that the child listens to (via the didSet method, it then updates its view accordingly. However, this isn't ideal because the entryDate and entry string fields are useless on their own, almost redundant in the parent.
class ParentViewController: UIViewController {
var entryDate: String?
var entry: String?
#IBAction func leavingView(){
self.entryDate = ""
self.entry = ""
self.dismiss(animated: true, completion: nil)
}
}
class ChildViewController: ParentViewController {
#IBOutlet var entryDateLabel: UILabel!
#IBOutlet var entryLabel: UILabel!
override var entryDate: String? {
didSet {
guard isViewLoaded else {
return
}
entryDateLabel.text = entryDate
}
}
override var entry: String? {
didSet {
guard isViewLoaded else {
return
}
entryLabel.text = entry
}
}
}
In my opinion, the second solution is clearer and keeps implementation details more separate because you're using instructions or events to notify the child view controllers.
class ParentViewController: UIViewController {
#IBAction func leavingView(){
self.dismiss(animated: true, completion: didLeaveView)
}
func didLeaveView() { }
}
class ChildViewController: ParentViewController {
#IBOutlet var entryDateLabel: UILabel!
#IBOutlet var entryLabel: UILabel!
override func didLeaveView() {
entryDateLabel.text = ""
entryLabel.text = ""
}
}
Since your requirement is not that much clear I have created a demo for you and into that demo I have added child ContainerViewController into parent ViewController and from that parent view controller you can change UILabel text when you click on UIButton of parent ViewController and code will be for ViewController
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func btnFromParentViewTapped(_ sender: Any) {
//Here get the child of your parent view controller
if let containerView = self.children[0] as? ContainerViewController {
containerView.lblContainer.text = ""
}
}
}
and ContainerViewController code will be:
class ContainerViewController: UIViewController {
#IBOutlet weak var lblContainer: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
}
Don't need to add much here because you are accessing it from parent view.
And your result will be:
As you can see when I click on button which title says Change Container label text the label text from ContainerViewController set to empty string.
For more info check THIS demo project.

Why could not cast value of type 'UIView' to other View?

This question deals with the same code with my previous question.
(The previous question is Why is this View nil in awakeFromNib()?)
But totally different approach.
It's not the duplicated question.
So let me restructure my question.
First, let me summarize my project.
Thanks to previous answer, I can reuse xib now.
This is CardContentView.xib
Codes are below
#IBDesignable class CardContentView: UIView {
#IBOutlet weak var backgroundView: UIView!
#IBInspectable var nibName:String?
var contentView: UIView?
override func awakeFromNib() {
super.awakeFromNib()
xibSetup()
}
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
xibSetup()
contentView?.prepareForInterfaceBuilder()
}
func xibSetup(){
guard let view = loadViewFromNib() else { return }
view.frame = self.bounds
view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
addSubview(view)
contentView = view
}
func loadViewFromNib() -> UIView? {
guard let nibName = nibName else { return nil }
let bundle = Bundle(for: type(of: self))
let nib = UINib(nibName: nibName, bundle: bundle)
return nib.instantiate(withOwner: self, options: nil).first as? UIView
}
}
And this is CardView.xib, the property follows 'class CardContentView'
Codes are below
class CardView: UIView {
#IBOutlet weak var cardContentView: CardContentView!
override func awakeFromNib() {
cardContentView.layer.cornerRadius = 16
}
}
This is Main.storyboard. with a single UIView.
Codes are below
class ViewController: UIViewController {
#IBOutlet weak var cardView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
}
}
But, I want to reuse class 'CardView' to 'cardView' property in ViewController.
I want to get 'background' property to 'cardView' property (In simple words, I want to change the yellow box to the blue box in CardContentView!).
And I want to make 'cardView' property with a transition effect.
But, I cannot make extension because it's not in a UIView class, but in a ViewController class.
So I need to make another UIView class.
That's why I made a CardView class separately.
(Of course, I want to organize codes neatly by making another file.)
So, I tried 2 different ways.
Set the property 'cardView' class as CardView, and connect it to ViewController.
class ViewController: UIViewController {
#IBOutlet weak var cardView: CardView!
override func viewDidLoad() {
super.viewDidLoad()
}
}
Cast 'CardView' class to other property in ViewController
override func viewDidLoad() {
super.viewDidLoad()
let tappedView = self.cardView as! CardView
self.view.addSubview(tappedView)
}
But both of them make errors.
1. When I made '#IBOutlet weak var cardView: CardView!', it throws error message, 'Unexpectedly found nil while unwrapping an Optional value, cardContentView = nil'.
2. When I made 'let tappedView = self.cardView as! CardView', it throws error message, 'Could not cast value of type 'UIView' (0x10a229ff8) to 'AwakeFromNibTutorial.CardView' (0x1060aa8d0).'
How can I solve this problem?
Please help me!
In the CardView class's awakeFromNib(), you are not calling super.awakeFromNib().
You would have to use the same logic (xibSetup()) that you used to load the CardContentView from it's corresponding XIB, to load the CardView also from its XIB.
In the ViewController in your storyboard, have you assigned the class of the UIView to CardView?

Xib, Label text Change

How do I work with a Label that hangs on a xib?
lblName.text = "Label Text"
I am getting error associated with
unexpectedly found nil while unwrapping an Optional value
You should update your custom View class in the following way.
First of all, open View.xib file set File's Owner custom class to View like the image in given below
after set UILabel IBOutlets like the image given below
After that open View.swift file and paste the code given below
class View: UIView {
private var view: UIView!
#IBOutlet weak var name: UILabel!
#IBOutlet weak var firstname: UILabel!
override init(frame: CGRect) {
super.init(frame: frame)
xibSetup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
xibSetup()
}
//FIXME:- Set up
func xibSetup() {
view = loadViewFromNib()
view.frame = bounds
view.autoresizingMask = [UIViewAutoresizing.flexibleWidth, UIViewAutoresizing.flexibleHeight]
addSubview(view)
}
func loadViewFromNib() -> UIView {
let bundle = Bundle(for: type(of: self))
let nib = UINib(nibName: "View", bundle: bundle)
let view = nib.instantiate(withOwner: self, options: nil)[0] as! UIView
return view
}
}
Now you can variable of View class in ViewController.