Storyboard / Swift all IBOutlets and _view are nil - swift

I'm working on a Swift app and running into a crazy odd problem. I have a simple storyboard view controller setup, it has 3 UIButtons a UIImageView and a UIView (as a subview of the main view).
I want to programmatically add a WKWebView to the UIView.
Easy enough right? All of the above are in the Storyboard View, declared in the custom UIViewController class and connected in IB. However at run time, everything is nil. This is a snippet of my code:
#IBOutlet var button1 : UIButton!;
#IBOutlet var button2 : UIButton!;
#IBOutlet var button3 : UIButton!;
#IBOutlet weak var containerForWebView: UIView!
var webView: WKWebView!
override func loadView()
{
}
override func viewDidLoad()
{
super.viewDidLoad()
displayWebPage()
}
override func viewWillAppear(_ animated: Bool)
{
super.viewWillAppear(animated)
}
override func viewDidLayoutSubviews()
{
super.viewDidLayoutSubviews()
}
private func displayWebPage()
{
webView = WKWebView()
webView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
webView.frame = CGRect(origin: CGPoint.zero, size: containerForWebView.frame.size)
webView.navigationDelegate = self
containerForWebView.addSubview(webView)
let url = URL(string: "https://www.google.com")!
webView.load(URLRequest(url: url))
}
When the code calls the displayWebPage() method I break on the first line. In the debugger you can see all of the UIViewController properties are nil. The IBOutlets are Nil and the _view variable of the UIViewController itself is nil.
I don't understand why this is happening. Everything is pretty simple and easily connected. I never ran into this sort of issue in Objective-C. Any advice is appreciated!

Remove loadView implementation. Do not override that method, always use viewDidLoad instead.
If you override loadView then you are responsible for creating controller's view and assigning it to self.view.

Related

How to show containerview in viewcontroller in swift

I need to show containerview in viewcontroller, so i have taken one viewcontroller and designed and in that i have taken one containerview below to the buttons view
given containerview constraints: below to the buttonview
top = buttonview.bottom 10, leading, trailing, bottom = 0
there are two buttons indicates with green line.. initially i am in the "viewproposalvc" with red colour button.. here if i click gray colour button then i need to show containerview
here the viewproposalvc code:
class ViewProposalVC: UIViewController, UITableViewDelegate, UITableViewDataSource {
var postId: Int?
#IBOutlet weak var containerView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
updateNavigationBar(with: "View Proposal")
containerView.isHidden = true
}
#IBAction func grayBtn(_ sender: Any) {
containerView.isHidden = false
let vc = UIStoryboard.init(name: "Main", bundle: Bundle.main).instantiateViewController(withIdentifier: "ContainerVC") as? ContainerVC
vc.serviceId = self.viewproposalData?.result?.posted_services?.id
self.navigationController?.pushViewController(vc!, animated: true)
}
#IBAction func redBtn(_ sender: Any) {
containerView.isHidden = true
postedServicesCall()
}
}
this is containervc code:
class ContainerVC: UIViewController, UITableViewDelegate, UITableViewDataSource {
override func viewDidLoad() {
super.viewDidLoad()
}
}
but here containervc not coming below to the buttonsview.. its coming in separate viewcontroller, why?
o/p of containervc: i dont want separate viewcontroller.. i need containervc to show below to the buttonsview of the viewproposalvc
first time i am working with container views, please do help
The reason why is because you are calling
self.navigationController?.pushViewController(vc!, animated: true)
ContainerView is just a generic UIView with an embedded segue, that is triggered from viewDidLoad of the hosted VC. If you need to do additional setup of that VC I suggest making an var on ViewProposalVC and making desired changes from there.
as in
class ViewProposalVC: UIViewController, UITableViewDelegate, UITableViewDataSource {
var postId: Int?
#IBOutlet weak var containerView: UIView!
#IBOutlet weak var secondaryController: UIViewController!
override func viewDidLoad() {
super.viewDidLoad()
updateNavigationBar(with: "View Proposal")
containerView.isHidden = true
}
#IBAction func grayBtn(_ sender: Any) {
containerView.isHidden = false
self.secondaryController.serviceId = self.viewproposalData?.result?.posted_services?.id
}

Unexpectedly found nil in swift with UIProgressView

I've created a progressView and linked it like this:
And here is what I do in my view controller:
class ViewController: UIViewController, WKNavigationDelegate, WKUIDelegate {
#IBOutlet weak var progressView: UIProgressView!
#IBOutlet weak var KronosWebsite: WKWebView!
override func loadView() {
//initialize the webview
progressView = UIProgressView()
progressView.progress = 1.0
KronosWebsite = WKWebView()
self.view = KronosWebsite
self.view.addSubview(progressView)
}
}
And I've got
Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional
in this line progressView.progress = 1.0
Add super.loadView() when you use loadView for a vc that originally inside storyboard
override func loadView() {
super.loadView()
or insert all code inside viewDidLoad , also don't re-init
progressView = UIProgressView()
KronosWebsite = WKWebView()
as they already initiated from storyboard
The docs say to never override loadView if your view controller comes from a storyboard or xib:
loadView()
If you use Interface Builder to create your views and initialize the view controller, you must not override this method.
Everything that you're currently doing in loadView can be done in the storyboard anyway, so just remove the loadView method and connect your UIProgressView and WKWebView to your outlets in your storyboard. You can also set the progress view's initial progress to 1.0 inside the storyboard.

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?

delegate is always nil

I have a custom UIView class called MyView and a View Controller.
When the user taps a button on the UIView, I want to call a function on the view controller. I'm trying to achieve this through delegation
custom UIClass
#objc protocol MyViewDelegate{
optional func expandCollapse()
}
class MyView: UIView, UICollectionViewDelegateFlowLayout, UICollectionViewDataSource, UICollectionViewDelegate{
weak var delegate:MyViewDelegate?
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
if self.subviews.count == 0 {
loadNib()
}
}
override init(frame:CGRect){
super.init(frame: frame)
loadNib()
}
func loadNib(){
let bundle = NSBundle(forClass: self.dynamicType)
let nib = UINib(nibName: "MyView", bundle: bundle)
let view = nib.instantiateWithOwner(self, options: nil)[0] as! MyView
view.frame = bounds
view.autoresizingMask = [.FlexibleWidth, .FlexibleHeight]
self.addSubview(view);
}
#IBAction func expandit(sender: AnyObject) {
//this is where it fails. delegate is nil
delegate!.expandCollapse!()
}
}
My View Controller
class ViewController2: UIViewController, MyViewDelegate {
#IBOutlet weak var theview: UIView!
var myview : MyView?
override func viewDidAppear(animated: Bool) {
super.viewWillAppear(animated)
myview = MyView(frame: CGRectMake(0,0,theview.frame.size.width,theview.frame.size.height))
self.theview.addSubview(myview!)
myview!.delegate = self
}
func expandCollapse() {
viewheight.constant = 172
UIView.animateWithDuration(0.5) {
self.view.layoutIfNeeded()
}
}
In the UIView, the delegate is always nil. What am I missing?
Using delegation for this is simply unsuitable. You are fighting UIKit design patterns.
The whole situation is very simple.
You have your ViewController.
Then you have your totally independent custom view.
Essentially, you want somehow to route the TouchUpInside event from the button to get to viewController.
If your Custom view contains a button, then the accessibility level of this button is internal by default. Looking at the code, I assume you created the button in Interface builder. Make an outlet from the custom view class to the button, so that there is a programatically accessible reference to it.
Your view controller declares an instance of this custom view. Then, in viewDidLoad you have to use the target-action pattern.
self.customView.button.addTarget(target: self, action: "expandCollapse", forControlEvents: .TouchUpInside)
That's basically all there is to it.
I'm not entirely confident of my ARC understanding, but I believe the issue is that your delegate is a weak reference and there's nothing keeping a reference to the delegate after it's set, so it' deallocated.
Replace it with this and I believe it will work:
var delegate:MyViewDelegate?
Try assigning the delegate to "myview" before adding it to "theview"
The problem is in the loadNib() member function.
You're creating two instances of "MyView". The second instance being added as a subview.
You're setting the delegate in one instance and referring to a nil delegate in the other instance.
Try using a static class method like below to create one instance of "MyView"
class func loadFromNib() -> MyView? {
guard let myView = Bundle.main.loadNibNamed("MyView", owner: nil, options: nil)?.first as? MyView else {
assertionFailure("Failed to load nib for 'MyView'!")
return nil
}
return myView
}
Doing it this way, you won't need the custom init()s either.
Hope that helps!

Swift Text Label Nil Even With Default Value

This is driving me crazy. The function updateTextView() is being called, verified by the print statements, but it is not setting the label in my view controller, and the print statements for the label are returning nil even though it has a default value set which is visible when the app is loaded. Whats more perplexing is that I set up a test button to call this function separately, and when I call it with test(), then the label updates properly.
class GoalDetailViewController: UIViewController, TextDelegate {
#IBAction func test(sender: AnyObject) {
updateTextView()
}
func updateTextView() {
print(goalSummaryTextBox?.text)
print("delegate called")
self.goalSummaryTextBox?.text = GoalsData.summaryText
print(goalSummaryTextBox?.text)
}
#IBOutlet weak var goalTitle: UILabel?
#IBOutlet weak var goalCreationDate: UILabel?
#IBOutlet weak var goalSummaryTextBox: UITextView?
override func viewDidLoad() {
super.viewDidLoad()
goalSummaryTextBox?.text = GoalsData.summaryText
}
}
updateTextView() is being called through a delegate method after I pop a different view controller, as can be seen below:
class TextEditViewController: UIViewController {
var textDelegate: TextDelegate?
#IBOutlet weak var textView: UITextView?
func configureView() {
navigationItem.title = "Edit Description"
navigationItem.setRightBarButtonItem((UIBarButtonItem(barButtonSystemItem: .Done, target: self, action: "segue")), animated: true)
}
func segue() {
textDelegate = GoalDetailViewController()
if let text = textView?.text {
GoalsData.summaryText = text
}
textDelegate?.updateTextView()
self.navigationController?.popViewControllerAnimated(true)
}
}
The line causing the issue is in the TextEditViewController below:
textDelegate = GoalDetailViewController()
What this line does is creates a new instance of GoalDetailViewController, and sets it as the delegate to the TextEditViewController. But, what you want is the original instance of GoalDetailViewController to be the delegate. This is why you were seeing the logs when popping TextEditViewController, since it was executing the other instance (which wasn't part of the view hierarchy). It also explains why all your IBOutlets are nil when stepping through updateTextView() on the delegate call, and that the button you added updates the text properly.
The solution is to make the delegate connection in the prepareForSegue method:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let destination = segue.destinationViewController as? TextEditViewController {
destination.textDelegate = self
}
}
Add the above code to your GoalDetailViewController.
EDIT:
The below code will ensure that this problem does not happen in the future. Change the delegate's definition to:
weak var textDelegate: TextDelegate?
and change your protocol to:
protocol TextDelegate: class {
func updateTextView()
}