performSegueWithIdentifier weird behavior - ios

I've setup a walkthrough view at the beginning of my app using BWWalkthrough and it works fine. Now I'm just trying to skip that view if the user has already seen it. Pretty simple right? But somehow it is not working. This is what I have:
On my storyboard I setup a segue between the BWWalkthroughViewController and the main view of my app and called it "RootView"
And this is my code:
class TutorialViewController: BWWalkthroughViewController {
#IBOutlet weak var endTutorialButton: UIButton!
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
// Check if should skip this viewcontroller
let shouldSkipTutorial = NSUserDefaults().boolForKey(Constants.UserPreferences.TUTORIAL_SEEN_KEY)
if shouldSkipTutorial {
// In debugger this part is reached but the segue is not executed
finishTutorial()
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Here I setup everything walkthrough related
}
// This is called when the user reaches the end of the walkthrough and press the finish button. The weird part is, this works fine! walkthrough is dismissed and next ViewController is pushed.
#IBAction func endTutorial(sender: AnyObject) {
NSUserDefaults().setBool(true, forKey: Constants.UserPreferences.TUTORIAL_SEEN_KEY)
finishTutorial()
}
func finishTutorial() {
self.performSegueWithIdentifier("ShowRoot", sender: nil)
}
}
Where can the problem be?

Related

iOS delegate is nil when trying to pass data back with Swift 5

I know this is a pretty common question but I've tried the various solutions offered here (that are not too old) and in numerous tutorials and I just can't seem to find out why it's still failing for me. Basically setting sendingViewController.delegate to self ends up being nil in sendingViewController. I understand this is very likely because the reference to the sendingViewController is being disposed of. But here is why I'm asking this again.
First, almost every tutorial and every other StackOverflow post is wiring up the mainViewController and the sendingViewController differently. I'm trying to make this work through a Navigation Controller, what one would think is the most common pattern for this.
In the app I'm building (which is more complex than the sample I'm going to show), the mainViewController calls the Settings viewController through a right navbar button. Then the user can select items from a list, which opens a controller with a searchBar and a tableView of items to select from. I need that third view controller to return the selected item from the table view to the settings screen. I'm using storyboards as well. I'm fairly new to Swift and I'm not ready to do all this "programmatically". Any way in the sending view controller, my delegate which should have been set in the calling view controller is nil and I can't invoke the protocol function in the main view controller to pass the data back.
I did a tutorial directly (not using Nav controllers) and I got that to work, but the moment I deviate away, it starts failing. I then put together a streamlined project with two view controllers: ViewController and SendingViewController. ViewController was embedded in a navigation controller and a right bar button was added to go to the SendingViewController. The SendingViewController has a single UI Button that attempts to call the protocol function and dismiss the SendingViewController. I'm not using Seque's, just a simple buttons and protocol/delegate pattern as I can.
My question is what am I missing to actually set the SendingViewController.delegate correctly?
Here's some code:
//ViewController.swift
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var showDataLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
#IBAction func fetchDataButton(_ sender: UIBarButtonItem) {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let controller = storyboard.instantiateViewController(withIdentifier: "SendingViewController") as! SendingViewController
controller.delegate = self
print("fetching data")
present(controller, animated: true, completion: nil)
}
}
extension ViewController: SendingViewControllerDelegate {
func sendData(value: String) {
print("got Data \(value)")
self.showDataLabel.text = value
}
}
and
// SendingViewController.swift
import UIKit
protocol SendingViewControllerDelegate {
func sendData(value: String)
}
class SendingViewController: UIViewController {
var delegate: SendingViewControllerDelegate?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
#IBAction func sendDataButton(_ sender: UIButton) {
print("attempting to send data \(self)")
print("to \(self.delegate)")
self.delegate?.sendData(value: "Hello World")
self.navigationController?.popViewController(animated: true)
}
}
Here is a screenshot of the Storyboard:
The ChildViewController does have a storyboard id name of "ChildViewController". All buttons and labels have their appropriate IBOutlet and IBAction's set up.
Help!
i copy paste your code .. its working perfect .. i make just one change
instead of pop you need to use dismiss as you are presenting from your base viewController
#IBAction func sendDataButton(_ sender: UIButton) {
print("attempting to send data \(self)")
print("to \(self.delegate)")
self.delegate?.sendData(value: "Hello World")
self.dismiss(animated: true, completion: nil)
}
here is the project link we.tl/t-NUxm9D26XN
I managed to get this working. In the receiving/parent view controller that needs the data:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let controller = segue.destination as! sendingViewController
controller.cityDelegate = self
}
Then in the sending view controller in my tableView did select row function:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
{
let city = filtered[indexPath.row]
searchBar.resignFirstResponder()
self.navigationController?.popViewController(animated: true)
self.cityDelegate?.addCity(city)
self.dismiss(animated: true, completion: nil)
}
I don't think I should be both popping the view controller and dismissing it, but it works. Also in the view controller I did this:
private var presentingController: UIViewController?
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
presentingController = presentingViewController
}
override func didMove(toParent parent: UIViewController?) {
super.didMove(toParent: parent)
if parent == nil {
}
}
I don't know if I really need this didMove() or not since it doesn't really do anything.
But some combination of all this got it working.
In my other app I'm not using a navigation bar controller and the standard delegate/protocol method works like a charm.

Can't add functionalities to my UIPageViewController's UIViewControllers

I've made a UIPageViewController, which will administrate 3 UIViewControllers.
It all seems to work just fine, right until I add a button in the storyboard, and hook it up with a IBOutlet - then my app crashes with:
"libc++abi.dylib: terminating with uncaught exception of type NSException"
Also, if I hook the button up with an IBAction, the app crashes when tapping the button.
class PostPageViewController: UIPageViewController {
override func viewDidLoad() {
super.viewDidLoad()
print("IN PAGE VC!!")
self.dataSource = self
if let firstViewController = orderedViewControllers.first {
print("IN FIRST!! \(firstViewController)")
setViewControllers([firstViewController], direction: .Forward, animated: true, completion: nil)
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
private(set) lazy var orderedViewControllers: [UIViewController] = {
return [self.newViewController("1"), self.newViewController("2"), self.newViewController("3")]
}()
private func newViewController(number: String) -> UIViewController {
return self.storyboard!.instantiateViewControllerWithIdentifier("NewPostVC\(number)")
}
}
I have an extension controlling the pages as well, but I don't think that's interesting in this situation.
Here's my first view controller, thats causing the problem:
class NewPostVC1: UIViewController {
#IBOutlet weak var cancelButton: UIButton!
#IBAction func cancelButtonTapped(sender: AnyObject) {
print("Cancel button tapped!")
}
override func viewDidLoad() {
super.viewDidLoad()
print("NEWPOSTVC 111111")
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
So, as said, the app creashed when tapping the button - I guess it's something about the UIPageViewController not being able to figure out which UIViewController is interacting, but I haven't been able to find anything that confirms this. Does anyone have an idea about this issue?
UPDATE: I just figured out that mu first viewController isnt instanciated, but number 2 and 3 are.. So right now my issue is that I have to load my viewcontroller somehow, before its shown - OR simply just dont have any functionality on the first page, and only on number 2 and 3.. Does anyone have a suggestion for instanciate the first viewcontroller before its shown? I've tried storyboard.instantiateViewControllerWithIdentifier("NewPostVC1"), but it doesn't do the job!
Those kinds of error usually mean you have hooked something up incorrectly on the storyboard. E.g. you've added an IBAction, hooked it up to the button action but then gone back to the code and changed the name of the method (by accident or on purpose).

Why all IBOutlet(s) found nil when viewdidload is called in App Delegate?

My Xcode debugger reports, every other values include alive (bool value) is set, except all the IBOutlets found nil (myLine, etc.). By the way, everything works when I delete all the code in App Delegate, but I need this view to update frequently, so implement it in applicationWillEnterForeground is necessary. And another thing worth pointing out, in the configure view 1 and 2, I set each outlet's value. And I make app delegate call viewdidload method before that, so all the outlets should hooked up with code already, so those outlets shouldn't be nil.
An error message in the debugger -- fatal error: unexpectedly found nil
while unwrapping an Optional value
import UIKit
class ViewController: UIViewController {
var alive : Bool = true
var aliveDefault = NSUserDefaults.standardUserDefaults()
//IBOulet Connections
#IBOutlet weak var myLine: UILabel!
#IBAction func buttontapped(sender: AnyObject) {
alive = false
NSUserDefaults.standardUserDefaults().setObject(alive, forKey: "alive")
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
loadView()
}
func loadView(){
alive = aliveDefault.boolForKey("alive")
if alive = true {
configureView()
}else{
configureView2()
}
}
func configureView(){
myLine.text = "Random text"
}
func configureView2(){
myLine.text = "Random text 2"
}
}
App Delegate
func applicationWillEnterForeground(application: UIApplication) {
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
ViewController().viewDidLoad()
ViewController().configureView()
}
Since you are creating two new instances of ViewController in applicationWillEnterForeground using the default initialiser rather than from the storyboard, none of the IBOutlets will be set. You need to update the current instance of the view controller that is on screen.
Rather than doing this from the appDelegate it is probably easier to have your view controller subscribe to the UIApplicationWillEnterForegroundNotification NSNotification and handle the refresh locally. This way you don't need to closely couple your app delegate and your view controller and you don't need to worry if the view controller isn't currently on screen:
class ViewController: UIViewController {
...
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(ViewController.loadView), name: UIApplicationWillEnterForegroundNotification, object: nil)
}
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
NSNotificationCenter.defaultCenter().removeObserver(self)
}
Do ViewController.loadViewIfNeeded() before assigning values to IBOutlets

Performing Segue using code gives shows black screen

I am trying to do a segue to another screen using code but it shows me a black screen using Xcode 7 beta 6.
here is my 1st view controller file code
// ViewController.swift
// Segue through programming
//
import UIKit
class ViewController: UIViewController{
#IBAction func buttonPressed(sender: AnyObject) {
presentViewController(secondController(), animated: true) { () -> Void in
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
This will only work if secondController programmatically creates its view. If you want to use a storyboard scene (which is far more common), you can do the following:
#IBAction func buttonPressed(sender: AnyObject) {
let controller = storyboard?.instantiateViewControllerWithIdentifier("foo")
presentViewController(controller!, animated: true, completion: nil)
}
That obviously assumes that you've specified a storyboard identifier for the destination scene.
Or, you can created a segue between the two scenes in IB by control dragging from the view controller icon at the top of the first scene to the second scene:
and then give that segue its own storyboard id:
Then you can invoke the segue programmatically:
#IBAction func buttonPressed(sender: AnyObject) {
performSegueWithIdentifier("bar", sender: self)
}

Updating a UITableView in Swift from another ViewController

I have a split view controller. Everything loads properly, except when you're in portrait and you rotate to landscape, the cell current gets de-selected.
I found the problem. The table was reloading data every time the viewWillAppear function was called, and viewWillAppear is called every time the device is rotated.
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
table.reloadData()
}
Now there's a new issue. I need to update the TableView whenever I add an item from a modal view.
The modal view is another view controller. I tried:
MasterViewContoller().table.reloadData()
That raised a bunch of flags and I'm pretty sure that's not the right way to do it. So how can I reload the table from another view?
==============
For those think that the ViewWillAppear is not called on rotation, try this and see:
class MasterViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
println("rotated")
}
}
class DetailedViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
The standard solution to your problem is to use a delegate.
protocol ModalViewControllerDelegate {
func updateInModalViewController(sender: ModalViewController)
}
class MainViewController: UIViewController, ModalViewControllerDelegate {
func prepareForSegue(_ segue: UIStoryboardSegue, sender sender: AnyObject?) {
if let controller = segue.destinationViewController as? ModalViewController {
controller.delegate = self
}
}
// the rest should be pretty obvious
}

Resources