Can't add functionalities to my UIPageViewController's UIViewControllers - ios

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).

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.

How can I change the state of a UISwitch so that it appears in the off position?

I am trying to have a switch appear in the off state once a view loads. Not always, but only if a boolean value that I created("switchBool") is false.
I've tried using the two ways on the apple documentation website. The two ways are shown in my code example. One is commented out.
import UIKit
class ViewController: UIViewController {
var switchBool = false
#IBAction func switchControl(_ sender: UISwitch) {
//sender.isOn = switchBool
sender.setOn(switchBool, animated: true)
}
override func viewDidLoad() {
super.viewDidLoad()
}
}
The app is building and running without errors. However, I want the switch to be in the off state if "false" is assigned to the bool "switchBool", which in my example it is, but no matter what I try the switch always appears in the on state when the view loads up.
You need to create an IBOutlet for your switch(using the assistant editor button and storyboard). Then, you can just set the switch to be off in viewDidLoad:
override func viewDidLoad() {
super.viewDidLoad()
yourSwitch.setOn(switchBool, animated: true)
}
In the storyboard you can set it to be off in the Attribute inspector

performSegueWithIdentifier weird behavior

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?

Swift Custom UIViewController's label is nil [duplicate]

This question already has an answer here:
IBOutlet of another view controller is nil
(1 answer)
Closed 7 years ago.
I customized a viewcontroller with xib, and this viewcontroller has a label.
I have already connected the label with xib.
But when I use this label, it is nil.
What's wrong I did?
below is my code.
import UIKit
class MyViewController: UIViewController {
#IBOutlet weak var label: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
code to use this viewcontroller
let myViewController = MyViewController(nibName: "MyViewController", bundle: nil)
myViewController.label.text = "test"
presentViewController(myViewController, animated: true) { () -> Void in
println("ok")
}
Thank you.
The issue is you are trying to access the label before it is loaded.
The following changes may solve your problem;
import UIKit
class MyViewController: UIViewController {
var labelText: String!
#IBOutlet weak var label: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
//Setting Label text here
label.text = labelText;
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
and
let myViewController = MyViewController(nibName: "MyViewController", bundle: nil)
myViewController.labelText = "test"
presentViewController(myViewController, animated: true) { () -> Void in
println("ok")
}
From the documentation:
The nib file you specify is not loaded right away. It is loaded the first time the view controller's view is accessed. If you want to perform additional initialization after the nib file is loaded, override the viewDidLoad method and perform your tasks there.
You may access the label from viewDidLoad. If you need to access the label externally, make sure you have accessed the view property first to force the view to be loaded.
The view of the view controller is not loaded. You can either use the viewDidLoad() method inside your view controller or you load it by accessing the view property. It is lazy loaded and will load your view.
So before accessing viewController.view.label write print(viewController.view) to lazy load the view

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