I have two UIViewController. NewBuyViewController and NewTransformViewController. Now, I need to use some data from NewTransformViewController in NewBuyViewController. I've used this code in NewBuyViewController:
let presentingView1 = self.presentingViewController as! UINavigationController
let presentingView2 = presentingView1.presentingViewController as! NewTransferViewController
but it crashes in second line. What should I do?
Please always include the error message / console messages you receive.
You are getting an error because presentingViewController returns the currently presented modal viewController, on top of the viewController in question.
presentingView1 is a UINavigationController that doesn't have a modal viewController. It has a rootViewController, and possibly later on a collection of viewControllers.
The crash is because you are force casting ( as! ) the second presentingViewController, which is returning nil, because it doesn't exist. Any attempt to force cast a nil will crash. You should avoid force casting at all costs unless absolutely necessary.
Instead try accessing the rootViewController like so:
if let secondViewController = presentingView1.rootViewController as? NewTransferViewController {
// Do something with `secondViewController`
}
Related
I'm in front of a big issues, and the only reason I can't find a solution is because my lack of knowledge about swift and memory management in swift. So here is my concerns. I'm working in swift 4.0 and iOS 9.3
I'm actually making a picture gallery app with login/logout. Basic application.
I'm working in cleanSwift So I don't have those HUGE ViewControllers.
My application is separate in 3 VC : The login, the gallery and the settings (which contains the LogOut).
Here is my problem. When I log out, I want to create a new loginVC and clear all previous VC.
So I have my cleanMemory function which set all the UIImage to nil
func cleanMemory(request: Gallery.Request) { // Interactor
worker.cleanMemory(completionHandler: { (Value) in
self.interventions?.removeAll() // Interventions contains UIImages
self.interventionsSelected.removeAll() // InterventionsSelected contains UIImages
})
}
and then I delete the rests of the UIImage and the VC
func cleanMemory() {
interactor?.cleanMemory(request: Gallery.Request())
self.displayedInterventions.removeAll() // displayedInterventions contains UIImages
interactor = nil
router = nil
self.removeFromParentViewController()
self.navigationController?.popViewController(animated: true)
}
But when I create my new LoginVC.. my RAM didn't decrease.. And when I check the app memory, not a single VC was deleted.. And when I execute the loop Logout/Login 3 times, my app crash because I'm not well managing my RAM_
So where did I get wrong, and why ??
Thanks you for your answer.
EDIT: I was having 2 problems :
My completionHandler was keeping my VC alive
I was switching VC with .present, so that was keeping my VC in memory.
So you should change VC like that :
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let destinationVC = storyboard.instantiateViewController(withIdentifier: "LoginController")
let appDelegate = UIApplication.shared.delegate as! AppDelegate
appDelegate.window?.rootViewController = destinationVC
To remove viewController from memory you simply need to remove it from navigation stack. So when you call navigationController?.popViewController(animated: true) and back to previous view controller you already destroy that controller.
Then,
Here is my problem. When I log out, I want to create a new loginVC and clear all previous VC. So I have my cleanMemory function which set all the UIImage to nil
on logout it's good practice to stop all request but you don't need to do any changes to UI, because it takes some time and it doesn't need to "remove controller from memory". How to check if view controller completely removed from navigation stack? Simply write print statement in deinit func, compile code and go back from this view controller.
deinit {
print("ViewController deinit")
}
If this print works fine (you can see text in xcode console), you achieve the result - controller has been removed from navigation stack, but if there is no print result you probably forget to right manage your closures. For example
worker.cleanMemory(completionHandler: { (Value) in
...
})
this closure may hold your controller when your think that controller already deallocated and it means that your controller present somewhere in memory. To prevent these retain cycles you need to use [unowned self] of [weak self] (just google for this keywords, it's very easy to understand) like this:
// or you can use `[unowned self]`
worker.cleanMemory(completionHandler: { [weak self] (Value) in
guard let `self` = self else { return } // only for `weak` way
...
})
So, in this case there are nothing that can hold your controller alive after pop from navigation stack action or so.
These are simple rules that you should follow to write well managed code.
I am new developer and I am struggling with my Walkthrough screen for my application.
I have created it, but when I click continue to my main screen, the app crashes.
Here is the code I have under my "continue" button:
#IBAction func skipButtonTapped(_ sender: AnyObject) {
let nextView: FirstViewController = self.storyboard?.instantiateViewController(withIdentifier: "FirstViewController") as! FirstViewController
let appdelegate = UIApplication.shared.delegate as! AppDelegate
appdelegate.window!.rootViewController = nextView
}
and the error I get is :
Could not cast value of type 'UITabBarController' (0x19f33c0d8) to 'Iwin.FirstViewController' (0x100057220).
I think the Problem is in the name of the name of the identifier which in my case is "FirstViewController"
This is the storyboard ID of my TabBarController, which should be the first screen of my app.
Attached I have uploaded a picture too.
The error you are seeing probably has to do with the forced conversion (as!) you do on the first line of the function. You're doing the following:
Calling instantiateViewController(withIdentifier:), which Apple API Documentation says returns UIViewController (in this case, it's returning UITabBarController, a subclass)
Force converting a UITabBarController to a FirstViewController, which fails.
This is because forced conversion guarantees that the resulting value won't be nil, but it doesn't guarantee that it won't fail (crash). UITabBarController is simply not convertible, because it does not extend/implement FirstViewController.
My guess is that even though you've given that view controller the identifier "FirstViewController", but it's just a regular old UITabBarController. Double check in Interface Builder that this View Controller is actually an instance of your FirstViewController class.
Looking at your photo, the "Class" field has the default value of UITabBarController -- this should be the first thing you try to change. Fill in FirstViewController, and let me know if it works.
I have a file in Swift that holds all my queries. And when saving a record with saveOperation.perRecordProgressBlock this file call ChatView view controller and updates the progressBarUpdate function.
So far I can get the print within progressBarUpdate to print the progress just fine. But when I get to update progressBarMessage.setProgress(value!, animated: true) the application just crash with the following error: fatal error: unexpectedly found nil while unwrapping an Optional value
If I try to run progressBarMessage.setProgress(value!, animated: true) through viewDidLoad it updates the progress bar fine, no error. Which means the outlet is working just fine.
Other thing to consider, is that my print(".... perRecordProgressBlock - CHAT VIEW\(value)") works just fine. If gets the updates from Queris.swift. It is just the progressBarUpdate that is causing issues.
# my Queries.swift file option 1
saveOperation.perRecordProgressBlock = { (recordID, progress) -> Void in
print("... perRecordProgressBlock \(Float(progress))")
var chatView = ChatView()
chatView.progressBarUpdate(Float(progress))
}
# my Queries.swift file option 2
saveOperation.perRecordProgressBlock = { (recordID, progress) -> Void in
print("... perRecordProgressBlock \(Float(progress))")
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let chatViewController = storyboard.instantiateViewControllerWithIdentifier("ChatViewVC") as! ChatView
chatViewController.progressBarUpdate(Float(progress))
}
# ChatView view controller
func progressBarUpdate(value: Float)
{
print(".... perRecordProgressBlock - CHAT VIEW\(value)")
if (value as? Float) != nil
{
progressBarMessage.setProgress(value, animated: true)
}
}
The way you are instantiating the viewController is not the right way and hence the crash/nil val. viewController loads its view hierarchy only when something sends it a view message. The system will do this by its own when to put the view hierarchy on the screen. And it happens after calls like prepareForSegue:sender: and viewWillAppear: , loadView(), self.view.
So here your outlets are still nil since it is not loaded yet.
Just try to force your viewController to call self.view and then access the functions from that viewController.
var chatView = ChatView()
I'm going to go out on a limb here and say you are using storyboards/xibs. If so, the above would not be the correct way to instantiate a new view controller. Here's some information on the difference (the question refers to Objective-C but the concept is the same in Swift)
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let chatViewController = storyboard.instantiateViewControllerWithIdentifier("identifier-you-set-in-storyboard") as! ChatView
Where identifier-you-set-in-storyboard is set in the interface builder (the linked question is old but illustrates the concept, the field label might have changed in newer versions)
If by some off chance you are creating you are setting up your views in code (as opposed to storyboards), you'd need to call chatView.loadView() before chatView.progressBarUpdate.... (Or just try to access the view property and it should call loadView for you.)
I have a UIViewController that I have had in a storyboard for a while with no problems. As my application grew, and I was using that view controller in more and more places, I realized that I should probably make it more portable, rather than have so many segues to it from hither and yon across the board. I've done splits like this before, so I did what I figured was logical here. I selected that view controller, cut it, and pasted into an empty .xib file. After changing each call to performSegueWithIdentifier to an init(nibName:bundle:) and presentViewController, I get a crash, with an object found unexpectedly nil in viewDidLoad()...
I set the value of this object after each init(...) call, just before presenting the view controller. The nil object is called from viewDidLoad(). This is a problem. I just set this, and now it's gone?!
I overrode the init(...) method, and found that self in init(nibName:bundle:) doesn't have the same memory address as self in viewDidLoad(). Also strange.
I overrode the other init() methods, and found that, after I call to present my view, my object is being instantiated again via init(coder:)! The self in here happens to be the exact self where my property is found nil!
The only reason I see for init(coder:) to be called at all is that I am loading my view from a .xib, but I thought this was handled in init(nibNamed:bundle:)? According to the docs, I do indeed get a call to init(coder:) if I'm loading from a storyboard, and doesn't touch the former... It also says that the nib isn't loaded until the controller's view is queried. If I understand it correctly, my view shouldn't get queried until I present the view. As the crash happens only when I present it, the issue likely stems from that.
I'm stuck here. I still need to get this contextual information to the view controller before it's presented. I've even tried making a proxy class to do the instantiating and property setting before presentation, but I still can't shake this second instance! I get one from init(nibName:bundle:), and another from init(coder:). Neither gets presented, and the latter gives me a nil object error. Any help at all in understanding why this is, and how I might work around this bug (feature?) would be much appreciated. Thank you!
Update:
On a whim, I decided to paste the view controller back into the storyboard, separate from the main hierarchy, and try instantiating it by its identifier. It worked! Not entirely sure how, but by George it worked! Now my question is this: Why?? What is so terribly evil and taboo about .xibs that Xcode and iOS won't tell me? I'm not a little flummoxed by this behavior. I'll keep trying with the .xib, if only to keep Xcode from yelling at me about entrance points...
I don't know what dark magic Xcode is doing, but here's two helper methods I wrote to easily instantiate any Storyboard VC - you just need the Storyboard name and VC identifier (optionally, otherwise will initial VC). By splitting up my VCs into many different Storyboards, I avoid dealing with xibs while still keeping things simple. One loads it into a nav controller of your choice, the other just returns it by itself:
struct StoryboardHelper {
///instantiates a VC with (optional) identifier viewController from storyboardName, pushes it to hierarcy of navigationController, and runs setup block on it, animated specifies whether the push is animated
internal static func showStoryboard(storyboardName: String, viewController: String?, navigationController: UINavigationController, animated: Bool = true, setup: (UIViewController) -> () ){
let storyboard = UIStoryboard(name: storyboardName, bundle: nil)
let destinationVC = viewController != nil ? storyboard.instantiateViewControllerWithIdentifier(viewController!) : storyboard.instantiateInitialViewController()!
setup(destinationVC)
navigationController.pushViewController(destinationVC, animated: animated)
}
///instantiates and returns a VC with (optional) identifier viewController from storyboardName
internal static func instantiateViewControllerFromStoryboard(storyboardName: String, viewController: String?) -> UIViewController{
let storyboard = UIStoryboard(name: storyboardName, bundle: nil)
return viewController != nil ? storyboard.instantiateViewControllerWithIdentifier(viewController!) : storyboard.instantiateInitialViewController()!
}
}
I have a function in my app delegate which appends an item to an array in my a file named ViewController.swift, and then I want to reload a tableview in that same file. I attempt to do so like so:
let vc = ViewController()
let navigationController = UINavigationController(rootViewController: vc)
window!.rootViewController = navigationController
println(vc.messages)
//1 vc.messages.append(message.data.message as! String)
//2 vc.MessageTableView?.reloadData()
The lines numbered 1,2 are where I set breakpoints. It appears to break after line numbered 2.
The exact error I get is
fatal error: unexpectedly found nil while unwrapping an Optional value
I believe my problem is that I am setting the root view controller incorrectly.
Edit #1
Window is declared up top.
If i run my code like this:
let vc = ViewController()
messages.append(message.data.message as! String)
vc.MessageTableView?.reloadData()
println(messages)
Then I can see that the correct data is put into the array (Which i made global for this scenario) but the table does not get updated in the View controller.
You have to reload data inside your ViewController
Put the reload inside viewDidAppear() or sth like that
It's an error because you just init a controller, but your views is not ready yet, they are being initialized.
After viewDidLoad, you can call your views, but not before.
So that's why your table is nil
Beside that, why do you need to reload your table even when you don't see it, right ?
What is the implementation of your delegate function?
After you put the tableview in the storyboard,You have to connect the tableview to the ViewController.swift.
Control drag from the tableview to the top of the viewcontroller (the yellowcircle) , there will be a black popup , do that two times and for each time click on datasource and delegate.
Then in the viewcontroller.swift file implement the functions they show in this tutorial. You don't have to modify the AppDelegate.swift file!
https://www.weheartswift.com/how-to-make-a-simple-table-view-with-ios-8-and-swift/
if you want you viewcontroller to be the rootviewcontroller al you have to do is click on the viewcontroller in storyboard and then in the utilities-> attributes inspector -> check the box "is initial view controller"
https://www.dropbox.com/s/qfd9jqeos7ueq1w/Screen%20Shot%202015-06-25%20at%207.40.46%20PM.png?dl=0