This question already has answers here:
What does "Fatal error: Unexpectedly found nil while unwrapping an Optional value" mean?
(16 answers)
Closed 6 years ago.
I'm having a problem, I have a UIButton in a UIViewController class and I want to enable that button after an animation that happens in a UIView class that is in another file.
class MainViewController: UIViewController {
#IBOutlet weak var nextButton: UIButton!
#IBAction func nextButtonPressed(sender: UIButton) {
nextButton.enable = false
}
}
When I try to call the nextButton from the viewController class after the animation is done I get this error:
EXC_BAD_INSTRUCTION(code = EXC_I386_INVOP, subcode = 0x0)
I get the error on the line where I set the nextButton enable to true.
class CustomView: UIView {
var vc = MainViewController()
func animationEnded() {
vc.nextButton = true
}
}
I don't have a clue what I'm missing and I would appreciate some help. Thanks
You encounter an error because in your CustomView you create a new MainViewController, you're not using the one initialized from the storyboard. That new MainViewController doesn't have any of its properties initialized, so nextButton is nil, hence the crash when you try to access it.
What you want to do is notify your controller from the view that the animation has ended so that the controller can update the button (since the controller owns the button). The standard way to do this in Cocoa is to use the delegate pattern like so:
class MainViewController: UIViewController, CustomViewDelegate
{
#IBOutlet weak var nextButton: UIButton!
#IBOutlet weak var customView: CustomView!
#IBAction func nextButtonPressed(sender: UIButton) {
self.nextButton.enabled = false
}
override func awakeFromNib() {
super.awakeFromNib()
self.customView.delegate = self
}
func customViewAnimationDidEnd(customView: CustomView) {
self.nextButton.enabled = true
}
}
protocol CustomViewDelegate : class
{
func customViewAnimationDidEnd(customView: CustomView)
}
class CustomView: UIView
{
weak var delegate: CustomViewDelegate? = nil
func animationEnded() {
self.delegate?.customViewAnimationDidEnd(self)
}
}
In this implementation the controller is the view delegate and gets notified when interesting events happen in the view (like a particular animation ending).
Make a delegate in your UIView that tells when it should hapen
protocol CustomViewDelegate {
func pushThatButton()
}
in CustomView class put this:
weak var delegate: CustomViewDelegate?
then
func animationEnded() {
delegate.pushThatButton()
}
and in UIViewController
class MainViewController: UIViewController, CustomViewDelegate {
and implement delegate ofc
func pushThatButton()
nextButton.sendActionsForControlEvents(.TouchUpInside)
}
almost forget, do an outlet to your view in viewController and setup delegate! in viewDidLoad() or when you will find this best
customViewOutlet.delegate = self
Related
I have a view controller that is responsible for adding a new object, say a new contact. This view controller (AddContactViewController) has the following UIBarButtonItem on a UINavigationBar, which is starts of disabled until enough information is provided to enable it. Then when this button is pressed a method (doneButtonPressed) is called.
The layout is as follows:
class AddContactViewController: UIViewController {
#IBOutlet weak var doneButton: UIBarButtonItem! {
didSet {
doneButton.isEnabled = false
doneButton.target = self
doneButton.action = #selector(self.doneButtonPressed)
}
}
#objc fileprivate func doneButtonPressed() {
// do some stuff ...
self.dismiss(animated: false, completion: nil)
}
}
As this is quite a common thing to have and there's a lot of boiler plate code, I've been working on a protocol AddingHandler but haven't quite worked out how to have UIBarButtonItem as a weak variable which hooks up to a storboard or if this is even the right way to go.
protocol AddingHandler {
var doneButton: UIBarButtonItem? { get set }
func doneButtonPressed()
}
extension protocol where Self: UIViewController {
func configureDoneButton() {
doneButton.isEnabled = false
doneButton.target = self
doneButton.action = #selector(self.doneButtonPressed)
}
}
Any help or comments in making this work would be much appreciated.
The problem How is best to add a weak UIButton to a protocol which can then be hooked up in a story board where UIViewController implements it? As there is a lot of repetitive code here should I wish to have another AddSomethingViewController I was wondering if there was a neater way of only writing this once (in a protocol with an extension) then calling the protocol in any view controller that is adding something new ...
You can simply configure the doneButton in viewDidLoad()
override func viewDidLoad()
{
super.viewDidLoad()
doneButton.isEnabled = false
doneButton.target = self
doneButton.action = #selector(self.doneButtonPressed)
}
Edit 1:
#objc protocol AddingHandler
{
var doneButton: UIBarButtonItem? { get }
#objc func doneButtonPressed()
}
extension AddingHandler where Self: UIViewController
{
func configureDoneButton()
{
doneButton?.isEnabled = false
doneButton?.target = self
doneButton?.action = #selector(doneButtonPressed)
}
}
class AddContactViewController: UIViewController, AddingHandler
{
#IBOutlet weak var doneButton: UIBarButtonItem!
override func viewDidLoad()
{
super.viewDidLoad()
configureDoneButton()
}
func doneButtonPressed()
{
// do some stuff ...
self.dismiss(animated: false, completion: nil)
}
}
I've used ObjC runtime to resolve the issue. Try implementing it at your end and check if it works for you.
I have an onboarding user flow:
Name -> Age -> Gender
Each of the screens shares the same structure:
Question (top)
Input (middle)
Continue (bottom)
I have a class OnboardingHelper.swift that creates a class to set the question box and continue button:
class UserOnboardingHelper{
var text: String
var questionbox: UIView
var viewController: UIViewController
var continueButton: UIButton
init(text: String, questionbox: UIView, viewController: UIViewController, continueButton: UIButton){
self.text = text
self.questionbox = questionbox
self.viewController = viewController
self.continueButton = continueButton
}
func setQuestionBox(){
//sets question box
}
func setContinueButton(){
//sets continue button
enableContinueButton()
addContinueButtonPath()
}
func enableContinueButton(){
//enables continue button
}
func disableContinueButton(){
//disables continue button
}
func addContinueButtonPath(){
//sets path of continue button based on which view
}
}
In each of the onboarding ViewControllers I am setting the class in ViewDidLoad():
class NamePageViewController: UIViewController, UITextFieldDelagate {
#IBOutlet weak var questionbox: UIView!
#IBOutlet weak var continueButton: UIButton!
#IBOutlet weak var inputLabel: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
let namePageSettings = UserOnboardingHelper(text: "What is your name", questionbox: questionbox, viewController: self, continueButton: continueButton)
namePageSettings.setQuestionBox()
namePageSettings.setContinueButton()
inputLabel.delegate = self
if nameIsFilled {
namePageSettings.enableContinueButton()
} else{
namePageSettings.disableContinueButton()
}
}
}
The issue is that in the ViewController I textFieldDidEndEditing() function which needs to call the namePageSettings class from viewDidLoad()
func textFieldDidEndEditing(_ textField: UITextField){
if (textField.text?.empty)!{
//I want to call disableContinueButton() from UserOnboardingHelper
} else {
//I want to enable enableContinueButton() from UserOnboardingHelper
}
}
Trying to understand if:
The overall approach is correct and if not, what's the best way
If the above approach is in the right direction, how should disableContinueButton() and enableContinueButton() be called?
Thanks in advance! Sorry if the approach is really dumb - I'm still trying to wrap my head around classes.
You can have the view controller have a weak reference to the onboarding helper, so you can still call helper methods without creating a retain cycle.
In NamePageViewController, add a property:
weak var userOnboardingHelper: UserOnboardingHelper?
Then, in UserOnboardingHelper's initializer, add:
self.viewController.userOnboardingHelper = self
You can now call the onboarding helper's methods in the view controller:
userOnboardingHelper.disableContinueButton()
userOnboardingHelper.enableContinueButton()
I am trying to update a UILabel with data retrieved from a different class, so in ViewDidLoad, I am calling a method in that different class, then in that class, calling a method in the ViewController to update the UILabel. Here is a simplified version of the code I'm trying to run:
import UIKit
class ViewController: UIViewController {
#IBOutlet var label: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
test().runTest()
}
func print() {
label.text = "test123"
}
}
and the other class:
import UIKit
class test: UIView {
func runTest() {
ViewController().print()
}
}
I keep getting an error message printed in the console saying: "fatal error: unexpectedly found nil while unwrapping an Optional value (lldb)"
And an error on the "label.text = "test123" saying: "Thread 1: EXC_Bad_Instruction(code=EXC_(386_INVOP, subcode = 0x0)"
Thanks in advance for your help.
Your label IBOutlet is nil because you are creating a new viewcontroller instance and your viewcontroller is not attached any story board. you can pass self to func runTest() and on self you can call print. Hope it will help!
You can use this
import UIKit
class ViewController: UIViewController {
#IBOutlet var label: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
test().runTest(self)
}
func print() {
label.text = "test123"
}
}
import UIKit
class test: UIView {
func runTest(controller:ViewController) {
controller.print()
}
}
Your problem is that you are running the function on a new instance of your view controller rather, you should be running it on the already created instance. For instance, if your view is a child of your view controller you can do this, in your class test replace edit your function test to be this.
func runTest() {
if let vc = self.parent as? ViewController{
vc.print()
}
}
This should run the function on the already created instance of your view controller.
I have 2 controllers
and have got 1 global variable, the problem is if I go to controller 2 and click on button northAmericaClick, it will navigate back to control 1, but the value of global variable won't change!
this is my code
controller 1
class OurViewController: UIViewController {
#IBOutlet weak var menuButton: UIBarButtonItem!
#IBOutlet weak var selectedServer: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
selectedServer.setTitle(selected server, forState: UIControlState.Normal) // selected server this is global variable
}
controller 2
class selectServerController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func northAmericaClick(sender: AnyObject) {
selectedserver = "North America"
self.navigationController?.popViewControllerAnimated(true)
}
From
You need to use a delegate. Here is an example how do use a delegate in Swift.
On your first ViewController, set your delegate when you load the second VC:
For example, if you are using the Storyboard Editor:
var secondViewController = (segue.destinationViewController.visibleViewController as MySecondViewControllerClass)
secondViewController.delegate = self
Write a Protocol and define a func to write you values back
For example, create a file called "Protocol.swift" and write something like that:
protocol writeValueBackDelegate {
func writeValueBack(value: String)
}
Add the function to your FirstViewController
func writeValueBack(value: String) {
// this is my value from my second View Controller
}
And to your ViewControllerClass
class ViewController: UIViewController, writeValueBackDelegate
Go to the Second View Controller, and add the delegate here:
class SecondViewController: ViewController {
// delegate for FirstViewController
var delegate: writeValueBackDelegate?
On your Second View Controller, you can now use this to call the func in the first View Controller an pass data.
delegate?.writeValueBack("That is a value")
You also need to indicate that your first view controller implements the protocol: class ViewController: UIViewController, writeValueBackDelegate {
A part of doing it with delegate you also can create singleton class ViewControllersDataModel class and share the variable using it:
import Foundation
class ViewControllersDataModel {
static let sharedInstance = ViewControllersDataModel()
var selectedserver: String = ""
private init() {
}
}
And call it like this:
ViewControllersDataModel.sharedInstance.selectedserver = "Selected Option";
Ok, I can do this with this code, only check when viewWillDisapear and call the parent of this view controller in the navicationController:
override func viewWillDisappear(animated: Bool) {
if ((self.navigationController!.viewControllers.last?.isKindOfClass(ActivityMyViewController)) == true){
let backView:MyViewController = self.navigationController!.viewControllers.last as! MyDetailViewController
backView // do whatever you want
}
}
I hope this code can help you, good luck
thanks guys for helping ;)
it was very simple
i just use then when it comeback ^^"
override func viewWillAppear(animated: Bool) {
selectedServer.setTitle(selectedserv, forState: UIControlState.Normal)
}
I am new to iOS programming and I'm trying to understand and begin implementing delegates to get information between view controllers and any other use they may have.
I've used this topic to get a little further, but I can't comment as i just created this account, so i can't ask a question on the post. I copied drewag's example but this line
#IBOutlet weak var delegate: ViewControllerBDelegate?
is giving me an error "IBOutlet property cannot have non-object type SecondViewControllerDelegate"
I deleted it and it runs but the information is not being sent between the two view controllers. I thought i was beginning to understand delegates but just getting them implemented is beginning to get frustrating. I've been at this for a few days now.
FirstViewController:
class FirstViewController: UIViewController, SecondViewControllerDelegate {
#IBOutlet weak var theMap: MKMapView!
func requiredText() -> String {
return "test"
}
SecondViewcontroller:
protocol SecondViewControllerDelegate {
func requiredText() -> String
}
class SecondViewController: UIViewController {
#IBOutlet weak var label: UILabel!
#IBOutlet weak var delegate: SecondViewControllerDelegate?
#IBAction func decide(sender: AnyObject) {
if let actualDelegate = self.delegate {
self.label.text = actualDelegate.requiredText()
}
}
So my question simply is what am I doing wrong? I thought i followed the example correctly.
You need to declare SecondViewControllerDelegate like this:
#objc protocol SecondViewControllerDelegate {
func requiredText() -> String
}
This is just a quirk of the Swift compiler or the runtime. The #objc directive makes the compiler emit additional information about the protocol. At runtime, the program uses that information to verify the delegate implements the protocol's methods. Since these objects are loaded from a xib (or storyboard), the compiler can't verify it at compile time.
You do not set your actualDelegate = firstViewController,so actualDelegate is always nil.
If you use storyboard,set Identifier of firstViewController as "first"
then
override func viewDidLoad() {
super.viewDidLoad()
let storyboard = UIStoryboard(name: "Main", bundle: nil);
var firstview = storyboard.instantiateViewControllerWithIdentifier("first") as FirstViewController?;
self.delegate = firstview;
// Do any additional setup after loading the view, typically from a nib.
}