Dismissing Pushed view controller without NavigationController - ios

I have two view controllers.
VC1 - Displays data in a tableView, selecting one of the cells goes to VC2.
VC2 - Show text fields to edit the data.
Question - After updating the data and going back to VC1, it does not show the updated data in the table.
I did try adding tableView.reloadData() in ViewWIllAppear but the ViewWillAppear method is not called when I dismiss VC2.
//CODE BELOW
VC2 -
#IBAction func saveTask(_ sender: Any) {
self.dismiss(animated: true, completion: nil)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(true)
view.endEditing(true)
if let task = task {
task.completed = toggleStatus.isOn
}
}
VC1 -
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
tableView.reloadData()
}

You have to update the item in the collection of your data that selected from table view
Example:
// The collection of your data is used to show in table view
var data: [String] = []
// After navigated back to the VC1, you have to update like:
data[you_selected_index] = inputData // From VC2
tableview.reloadData()
UPDATED
class VC1: UIViewController {
private var selectedIndex: Int?
}
extension VC1: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
selectedIndex = indexPath.row
let vc = VC2()
vc.delegate = self
present(vc, animated: true, completion: nil)
}
}
// MARK: - InputInfoVCDelegate
extension VC1: VC2Delegate {
func onInputInfoSuccessUpdated(source: String?) {
// Updating data here
guard let index = selectedIndex else { return }
data[index] = source
tableView.reloadData()
}
}
protocol VC2Delegate: class {
func onInputInfoSuccessUpdated(source: String?)
}
class VC2: UIViewController {
weak var delegate: VC2Delegate?
#IBAction private func actionTapToBackButton(_ sender: Any) {
delegate?.onInputInfoSuccessUpdated(source: inputTextField.text)
}
}

Related

UITableView not refreshing after dismissing ViewController above

I have a ViewController being presented using Apple's new default ModalTransitionStyle. And when that ViewController is dismissed, the ViewController below (that has a TableView) doesn't update the table.
I have tried ViewWill/DidAppear but in both cases, I get the error "Unexpectedly found nil while unwrapping an optional Value" when trying to access the table.
I have checked what methods get called and I found that numberOfRowsInSection gets called and cellForRowAtIndexPath doesn't get called. The table is for sure visible. It is not height 0 and numberOfRowsInSection doesn't get 0 returned.
I made a quick test project to demonstrate what I mean.
Image when App Runs
When Popup button clicked
When i go back
import UIKit
var cells = 5
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var table: UITableView!
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return cells
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "test")
return cell!
}
override func viewDidLoad() {
super.viewDidLoad()
table.reloadData()
// Do any additional setup after loading the view.
}
#IBAction func popup(_ sender: Any) {
performSegue(withIdentifier: "popover", sender: self)
}
}
import UIKit
class PopoverViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
cells += 1
// Do any additional setup after loading the view.
}
#IBAction func back(_ sender: Any) {
let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(identifier: "vc") as! ViewController
self.dismiss(animated: true)
}
}
What should happen is that everytime i go back to the view 1 more cell is being displayed. I checked if the count "Cell" is being increased and it is. I just cant figure out how to reload the table.
Old answer:
DispatchQueue.main.async { [weak self] in
self?.table.reloadData()
}
New Answer:
One of the your mistakes is that
let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(identifier: "vc") as! ViewController
self.dismiss(animated: true, completion: {
vc.table.reloadData()
})
The vc is not your living Main Controller, you've to send living controller's reference.
By the way.
class PopoverViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
cells += 1
// Do any additional setup after loading the view.
}
#IBAction func back(_ sender: Any) {
let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(identifier: "vc") as! ViewController
self.dismiss(animated: true, completion: {
vc.table.reloadData()
})
}
}
And your viewDidLoad method should be like:
override func viewDidLoad() {
super.viewDidLoad()
//table.reloadData()
// Do any additional setup after loading the view.
view.addSubview(table)
table.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
table.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
table.heightAnchor.constraint(equalTo: view.heightAnchor).isActive = true
table.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
NotificationCenter.default.addObserver(self,
selector: #selector(notify),
name: .notifier,
object: nil)
self.navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: UIBarButtonItem.SystemItem.action,
target: self,
action: #selector(hh))
}
hh function is just for programmatically present Popover
#objc private func notify(notification: NSNotification){
//do stuff using the userInfo property of the notification object
print("notification has called")
DispatchQueue.main.async {
self.table.reloadData()
}
}
extension Notification.Name {
static let notifier = Notification.Name("notifier")
}
And change your cells variable like this:
var cells = 5{
didSet{
NotificationCenter.default.post(name: .notifier, object: nil)
}
}
I've created a Notification pattern to messaging between pages. It worked for me. When you increase cells instance Notification has notified and cell's has updated.
As i understand ViewController is parent, PopoverViewController is child. After child is dismissed, parent should update tableview.
viewDidLoad is called once for parent, it wont be called for dismissal of child.
So, you may define any delegate or closure for child view controller, which reload tableview at parent. you can call it at completion of dismiss.
dismiss(animated: Bool, completion: (() -> Void)?)
class PopoverViewController: UIViewController {
var onDismiss: (() -> Void)?
//...
}
// Also add this method to viewcontroller
class ViewController: UIViewController {
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier = "popover" {
guard let popoverVC = segue.destination as? PopoverViewController else {
return
}
popoverVC.onDismiss = { [unowned self] in
self.tableView.reloadData()
}
}
}
Also you don't need to create parent for dismissal.
let vc = UIStoryboard(name: "Main".. this line is unnecessary. this new vc is not shown so if you try to reach it, crash will occur due to outlets' nil value.

Swift | Dismiss ViewController and Push other ViewController

Solved it: I forgot to declare tvx.selectionDelegate = self in VC1, thats why it never executed
I'v searched very long and fund two similar answers to my problem online:
here
But all of them work with dismissing and then presenting another view, and I want to dismiss and PUSH another ViewC.
I have 3 ViewController:
VC1 lets Call it: DownloadsViewController
VC2 lets Call it: SelectActionController
VC3 lets Call it: fileInfoView
VC1 presents VC2 modally then VC2 should dismiss and VC1 should PUSH VC3 immediately.
I've tried:
VC2:
self.present(vx, animated: true, completion: nil)
and the to put the PUSH animation in completion {} but it crashes.
The next thing I've tried is to make a delegate and if I press a button on VC2 it call VC1 to dismiss VC2 and Push VC3. Like Here:
VC2:
protocol PushViewControllerDelegate: class {
func pushViewController(_ controller: UIViewController)
}
class SelectActionController: UIViewController, UITableViewDelegate, UITableViewDataSource {
weak var delegate: PushViewControllerDelegate?
public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
{
if self.delegate != nil {
self.delegate?.pushViewController(self)}
VC1:
func pushViewController(_ controller: UIViewController) {
controller.dismiss(animated: true) { () -> Void in
self.performSegue(withIdentifier: self.segue2, sender: nil)
//let vx = self.storyboard?.instantiateViewController(withIdentifier: "FileInfo") as! fileInfoView
//self.navigationController?.pushViewController(vx, animated: false)
}
}
The Trird thing I tried was to make a global Variable and if it dismisses, just set a Bool variable to true and an func in VC1 should recognize it(my intention). Only problem it doesn't regognize dismissing as ViewDidAppear or stuff like that.
So none of that worked.
Does anyone has an Idea to this?
EDITED after OP comment
Just use a protocol.
Let your first controller adopt it, and add the required function.
Set a protocol variable in the second controller who takes the first one for reference and call the function when you dismiss the second controller.
You can now use the dismiss function to do whatever you want, pass data, send to an other controller...
first view controller :
protocol CustomProtocol {
func dismissed()
}
class AViewController: UIViewController, CustomProtocol {
#IBOutlet weak var button: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func toVC2(_ sender: UIButton) {
let VC = self.storyboard!.instantiateViewController(withIdentifier: "BViewController") as! BViewController
VC.customProtocol = self
VC.view.backgroundColor = .white
self.present(VC, animated: true, completion: nil)
}
func dismissed() {
let yourViewController = UIViewController()
yourViewController.view.backgroundColor = .red
guard let navigationController = self.navigationController else {return}
navigationController.pushViewController(yourViewController, animated: true)
}
}
second view controller :
class BViewController: UIViewController {
var customProtocol: CustomProtocol?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func quit(_ sender: UIButton) {
self.dismiss(animated: true) {
guard let proto = self.customProtocol else {return}
proto.dismissed()
}
}
}
According to your File from Github, I think you forgot to declare
tvx.selectionDelegate = self
in the
func imagePressed
I hope you need to perform the dismiss action on your
public func tableView(_ tableView: UITableView, didSelectRowAt indexPath:
in "SelectActionController" right ?
In such case you have to do as follows,
public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
{
if self.delegate != nil {
dismiss(animated: true) {
self.delegate?.pushViewController(self)}
}
}
I have shared an example project related to your situation, please refer it.
https://github.com/Bharath-MallowTech/StackoverflowSolutions/tree/master/viewcontrollerDismissAndPush
Could you post some code on what you've been trying? What is self here? Is it VC1, VC2 or VC3?
What you could do is have a look at protocols, so you use VC1 as a delegate and when you dismiss VC2 it will also call it's delegate showVC3(). You then place that delegate.showVC3() in the completion block for your dismiss.
// Declare it in VC1
#protocol MyDelegate {
func showVC3()
}
class VC1: UIViewController, MyDelegate {
var vc2: VC2ViewController = VC2ViewController()
var vc3: VC3ViewController = VC3ViewController()
func showVC2() {
vc2.delegate = self
self.present(self.vc2)
}
func showVC3() {
self.present(self.vc3)
}
}
// In VC2
class VC2: UIViewController {
var delegate: MyDelegate?
func closeVC2() {
self.dismiss(animated: true, completion: {
self.delegate?.showVC3()
})
}
}
Not sure if this works, haven't tried it out since I'm on my PC at the moment. But change it according to your needs and test it out.
Even i had the same situation , that after presenting a view controller , i need to push to some other view controller. If we have vc1, vc2, vc3, We presented vc2 from vc1, then on button click of vc2 we need to push to vc3, what i did is, i set vc3 as root view controller. You can check code as per below.
func goToHomeScreenPage(transition : Bool)
{
let _navigation : UINavigationController!
let startVC : TabbarViewController = Utilities.viewController(name: "TabbarViewController", onStoryboard: StoryboardName.Login.rawValue) as! TabbarViewController
_navigation = UINavigationController(rootViewController: startVC)
_navigation.setNavigationBarHidden(true, animated: true)
let transitionOption = transition ? UIViewAnimationOptions.transitionFlipFromLeft : UIViewAnimationOptions.transitionFlipFromLeft
gotoViewController(viewController: _navigation, transition: transitionOption)
}
the above method gotoviewcontroller() is below,
func gotoViewController(viewController: UIViewController, transition: UIViewAnimationOptions)
{
if transition != UIViewAnimationOptions.transitionCurlUp
{
UIView.transition(with: self.window!, duration: 0.5, options: transition, animations: { () -> Void in
self.window!.rootViewController = viewController
}, completion: { (finished: Bool) -> Void in
// do nothing
})
} else {
window!.rootViewController = viewController
}
}
Declare var window: UIWindow? on top, you can write these methods in appdelegate and call them by creating appdelegate instance.

Passing image between two VC using Modal Segue

I'm trying to pass data, using delegate between two VC, but I can't get why it's not working.
My first VC
class ViewController: UIViewController {
#IBOutlet weak var profileImage: UIImageView!
func downloadPhoto() {
self.performSegue(withIdentifier: "showPhotoFromInternet", sender: nil)
}
}
extension ViewController: IPresentaionPhotoFormInternetDelegate {
func setNewImage(imageToShow: UIImage) {
profileImage.image = imageToShow
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destination = segue.destination as? PresentPhotoFromInternetViewController {
destination.delegate = self
}
}
My second VC
class PresentPhotoFromInternetViewController: UIViewController {
var imageToSend: UIImage?
var delegate: IPresentaionPhotoFormInternetDelegate?
#IBOutlet weak var photoCollectionView: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
photoCollectionView.allowsMultipleSelection = false
}
#IBAction func sendPhotoToPreviousController(_ sender: Any) {
delegate?.setNewImage(imageToShow: iamgeToSend!)
performSegue(withIdentifier: "sendPhoto", sender: nil)
}
extension PresentPhotoFromInternetViewController: UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let cell = collectionView.cellForItem(at: indexPath) as! photoCollectionViewCell
print("Cell is selected")
iamgeToSend = cell.photoImageView.image
cell.selectedView.backgroundColor = UIColor.green
}
protocol IPresentaionPhotoFormInternetDelegate {
func setNewImage(imageToShow: UIImage)
}
I use present modaly segue for from the first VC to the second, and show form the second to the first
When I perform segue from the second VC there is no updates in my first one, although it passes all breakpoints.
The problem is instead of popping controller you are loading completely new controller in your button action
#IBAction func sendPhotoToPreviousController(_ sender: Any) {
delegate?.setNewImage(imageToShow: iamgeToSend!)
//Comment or remove the performSegue
//performSegue(withIdentifier: "sendPhoto", sender: nil)
//Now simply pop this controller
_ = self.navigationController?.popViewController(animated: true)
// If you are presenting this controller then you need to dismiss
self.dismiss(animated: true)
}
Note: If you are segue is kind of Push then use popViewController or is it kind of Modal than you need to use dismiss.

Dismiss Popover ViewController from Tableviewcell in Swift

In my iOS 8 app I've got a custom ViewController which I present as Popover. This ViewController has a delegate, which get, and send to parent ViewController, the clicked index in popup. The problem is that I can't dismiss this Popover after selectRow.
Here's the code:
This is the method which I call when I want to show my Popup.
#IBAction func registerButtonAction(sender: UIButton) {
popup = self.storyboard!.instantiateViewControllerWithIdentifier("PopupViewController") as? PopupViewController
popup!.modalPresentationStyle = .Popover
popup!.preferredContentSize = CGSizeMake(100, 120)
let popoverMenuViewController = popup!.popoverPresentationController
popoverMenuViewController?.permittedArrowDirections = .Up
popoverMenuViewController?.delegate = self
popoverMenuViewController?.sourceView = sender
popoverMenuViewController?.sourceRect = CGRect(
x: sender.frame.size.width/2,
y: sender.frame.size.height/2,
width: 1,
height: 1)
popup!.delegate = self
presentViewController(
popup!,
animated: true,
completion: nil)
}
Here's PopupViewController code:
protocol PopupViewControllerDelegate
{
func rowClickedAtIndex(var index : Int)
}
class PopupViewController: MainViewController {
var delegate : PopupViewControllerDelegate?
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
extension PopupViewController:UITableViewDelegate{
func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! {
var cell:PopupTableViewCell? = tableView.dequeueReusableCellWithIdentifier("PopupTableViewCell") as? PopupTableViewCell
if cell == nil {
cell = PopupTableViewCell(style: UITableViewCellStyle.Default, reuseIdentifier: "PopupTableViewCell")
}
return cell
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
if (self.delegate != nil)
{
self.delegate!.rowClickedAtIndex(indexPath.row)
self.dismissViewControllerAnimated(true, completion: nil)
}
}
func numberOfSectionsInTableView(tableView: UITableView?) -> Int {
return 1
}
func tableView(tableView: UITableView!, numberOfRowsInSection section: Int) -> Int{
return 2
}
}
Thank you.
I solved by myself:
I checked that the problem wasn't that the popover wasn't dismissed, but it was dismissed but after different seconds.
So I put my dismiss call in the main thread and it worked perfectly. Here's the code:
extension WelcomeViewController: PopupViewControllerDelegate {
func rowClickedAtIndex(index: Int) {
dispatch_async(dispatch_get_main_queue(),{
self.dismissViewControllerAnimated(true, completion: nil)
println(index)
})
}
}
I want to thank you Frankie, who helped me in finding the solution, removing what shouldn't be the problem.
Move your dismissViewControllerAnimated(true, completion: nil) call into the delegate at the end of the clickedRowAtIndex method. In other words, the presenting view controller should call the dismissing, not the presented view controller.
Try this:
extension WelcomeViewController: PopupViewControllerDelegate {
func rowClickedAtIndex(index: Int) {
dismissViewControllerAnimated(true, completion: nil)
println(index)
}
}

Get value from Modal View in Swift iOS

i'm trying to start writing Swift and i'm trying to get a value from a modal view controller with no luck.
I have two controllers, the ViewController and modalViewController.
In ViewController i have a UITableView and with a press of a button i open the modalViewController.
Then from a UITextField i pass the value.
I have implement a protocol with delegate and func but somewhere i'm missing something or had it wrong.
ViewController.swift
import UIKit
class ViewController: UIViewController,UITableViewDelegate,modalViewControllerDelegate{
#IBOutlet var table: UITableView!
var tableData = ["First Row","Second Row","Third Row"]
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func viewDidAppear(animated: Bool) {
table.reloadData()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func tableView(table:UITableView?,numberOfRowsInSection section: Int) -> Int
{
return tableData.count
}
func tableView(table:UITableView?,cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell!
{
let cell:UITableViewCell = UITableViewCell(style:UITableViewCellStyle.Default,reuseIdentifier:"cell")
cell.textLabel?.text = tableData[indexPath.row]
return cell
}
func sendText(text: NSString) {
tableData.append(text)
} }
modalViewController.swift
import UIKit
protocol modalViewControllerDelegate {
func sendText(var text: NSString)
}
class modalViewController: UIViewController{
let delegate: modalViewControllerDelegate?
#IBOutlet var textField: UITextField?
#IBAction func cancelButton(sender: AnyObject) {
dismissViewControllerAnimated(true, completion: nil)
}
#IBAction func saveButton(sender: AnyObject) {
delegate?.sendText(self.textField!.text)
dismissViewControllerAnimated(true, completion: nil)
}
override func viewDidLoad() {
super.viewDidLoad()
// Do view setup here.
}
}
I have no errors in the code, the delegate is not working, it's always nil.
Thanks.
You have to assign the delegate in your first view controller.
Also, you have to change let delegate: modalViewControllerDelegate? to a var, or else you can't change it.
Right now your delegate is empty.
It's unclear how you're accessing ModalViewController. If you're using segues:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "modalViewControllerSegue" {
var destination = segue.destinationViewController as CategoryViewController
destination.delegate = self
}
}
Or if you're doing it programmatically:
var modalViewController = ModalViewController(parameters)
modalViewController.delegate = self
presentViewController(modalViewController, animated: true, completion: nil)
Storyboard identifier:
let destination = UIStoryboard.mainStoryboard().instantiateViewControllerWithIdentifier("ModalViewController") as ModalViewController
delegate = self
showViewController(destination, sender: nil)
EDIT:
If you want to access ModalViewController by selecting a cell you need the tableView: didSelectRowAtIndexPath method:
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
performSegueWithIdentifier("modalViewControllerSegue", sender: self)
}
Using this, you'll need the method prepareForSegue to set the delegate.
You have to set your modalViewController's delegate property before presenting it. If you're using segues, you can do this in prepareForSegue(_:).
Also, class names should begin with uppercase letters (modalViewController should be ModalViewController). Only instances should begin with lowercase letters.
Another option, instead of using delegation, is to use an unwind segue. Here's a tutorial: http://www.cocoanetics.com/2014/04/unwinding/
In your case, in your presenting view controller you could have the method:
func returnFromModalView(segue: UIStoryboardSegue) {
// This is called when returning from the modal view controller.
if let modalVC = segue.sourceViewController as? ModalViewController
where segue.identifier == "mySegueID" {
let text = modalVC.textField.text
// Now do stuff with the text.
}
}
And then just link up everything in the Interface Builder as shown in the tutorial.

Resources