Updating a UITableView in Swift from another ViewController - uitableview

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
}

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.

Prepare for segue not getting called in UITabbarController

I have gone through all the other posts about this topic but they don't seem to help me.
I have a UITabBarController that is launching two tabs. I want to pass data collected in tab1 to the UITabBar ViewController. I am trying to use delegete protocol for this but I am having trouble setting the delegate variable in the sending ViewController. The prepare for segue never gets called. I cannot even cycle through the viewcontrollers of the tabs inside the ViewDidLoad() of the Tabbar controller as they are not created yet and so nil.
I have used delegates before and it seems rather straightforward. Does it matter that I am using it in a Tabbar?
When I run the code the viewDidLoad() in TabBarViewController is called but not the preparefor segue.
The IBAction donePressed in the MeViewController is called but the delegate is not called as its not set.
Here is the code --
protocol DetailsDelegate: class {
func myDetailsGathered( myDetails: MyDetails )
}
/// RECEIVING VIEW CONTROLLER
class TabBarViewController: UITabBarController, DetailsDelegate
{
override func prepare(for segue: UIStoryboardSegue, sender: Any?)
{
print("prepare for segue called\n");
if let destinationViewController = segue.destination as? MeViewController
{
destinationViewController.delegate = self
}
}
override func viewDidLoad()
{
print("ViewDidLoad Called \n")
}
func myDetailsGathered(myDetails: MyDetails)
{
self.myDetails = myDetails
print("My details gathered \n")
}
}
---------------
/// SENDING VIEW CONTROLLER
class MeViewController: UIViewController
{
weak var delegate: DetailsDelegate?
override func viewDidLoad()
{
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
}
// I have UIButton in the view and this is invoked when its pressed.
#IBAction func donePressed(_ sender: Any)
{
var infoToPass = MyDetails()
print("looks like we are done")
delegate?.myDetailsGathered(infoToPass: myDetails)
}
}
prepareForSegue is called when you perform a segue. Which you don´t do and that´s why it does not get called.
A segue defines a transition between two view controllers in your
app’s storyboard file.
You should use a singleton class to store variables and access them between different controllers. You declare one like this:
class Singleton {
static let sharedInstance = Singleton()
var name = ""
}
Assign to Singleton:
Singleton.sharedInstance.name = "Some name"
To read from it from whatever controller:
let name = Singleton.sharedInstance.name
First of all, why do you want your tabbarController to receive some info/data though?
The prepare for segue never gets called.
prepareForSegue method will be invoked right after the performSegue. So where's your performSegue method? Or are you sure that that kind of segue going to MeViewController is being performed?
One more option you have is to use NotificationCenter.

SWRevealViewController panGesture issue - IOS/Swift

In my application I used SWRevealViewController in order to implement the side menu. There, for some reason I had to put a view which is embed in a navigation controller, before the reveal view controller.
This is my storyboard
Everything works fine other than one thing. when I drag from the left edge of the screen to the right side (pan gesture) on my home view, instead of side menu it navigates me to the previous view (in here case to the middle view which contains a button in the middle).
This is how it looks like when dragging
I want to avoid this and get the side menu when dragging like that. Can someone help me on this. Any help would be highly appreciated.
Edit:
This is the front_view and rear_view
Take a ViewController.swift file for the initial ViewController having button.
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.navigationController?.interactivePopGestureRecognizer?.isEnabled = false
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
}
*/
}
There is no issue in the swrevealviewcontroller implementation but the native behavior of navigation controller is causing this issue.
You need to remove the popGesture from the navigation controller in any view controllers that you don't want to be able to use the swipe gesture:
var gestureRecognizer: UIGestureRecognizer? {
guard let nc = navigationController else { return nil }
return nc.interactivePopGestureRecognizer
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if let gr = gestureRecognizer {
gr.isEnabled = false
}
}

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?

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

Resources