UITableView not refreshing after dismissing ViewController above - ios

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.

Related

Dismissing Pushed view controller without NavigationController

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

How to present a separate View Controller when click on a button inside a custom TableViewCell Swift 4?

I have 2 view controller,MainVC and PopUpVC. MainVC have a tableView with tableViewCell class.Every cell have a ShowButton.So what I want to do is,when I click on ShowButton,PopUpVC will show on top of MainVc
What I already do:
1) Connect MainVC and PopUpVC with segue of present modally,at the same time set the segue identifier to "popVc".
2) Set PopUpVC Transition Style = Cover Vertical and Presentation = Over Current Context , Background of PopUpVC I set to Clear Color in StoryBoard.
3) In TableViewCell class for the TableView,set up the protocol and delegate like below :
//here is MyTableViewCell class
protocol MyDelegate : class {
func showPopUpVc(itemId: Int)
}
class MyTableCell : UITableViewCell {
weak var delegate : MyDelegate?
#IBAction func showButtonTapped(_ sender: Any) {
self.delegate?.showPopUpVc(itemId : MyItem.id)
}
}
//View Controller
class MainViewController: UIViewController ,UITableViewDataSource, UITableViewDelegate , MyDelegate {
func showPopVc(itemId; Int) {
//here I want to show the PopUpVC
self.performSegue(withIdentifier: "popVc", sender: self)
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "MyTableCell", for: indexPath) as! MyTableCell
cell.items = self.items[indexPath.row]
cell.delegate = self
return cell
}
}
4) So inside showPopVc() function in MainViewController I tried this 2 solution in order to show the popUpVC
//1st solution
func showPopVc(itemId; Int) {
self.performSegue(withIdentifier: "popVc", sender: self)
}
//2nd solution
func showPopVc(itemId; Int) {
let storyBoard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let PopUpVc = storyBoard.instantiateViewController(withIdentifier: "PopUpVc")
self.present(PopupVc, animated: true, completion: nil)
}
Output that I get now :
Whenever I tap the ShowButton, the tap is detected(Cause I set print("Clicked") in showPopUpVc()), but the whole screen become black.None of the content in PopUpVC is shown on the screen.Just black.Cant go back to previous screen,cause is absolutely black and blank screen
Both solution in section 4 also produce the same output.
So what I missing here? Or can somebody show me a complete example to solve this problem?
Try this
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if(segue.identifier == "popVc")
{
let newVC = segue.destination;
self.setPresentationStyleForSelfController(selfController: self,presentingController: newVC)
}
}
func setPresentationStyleForSelfController(selfController:UIViewController,presentingController:UIViewController)
{
if #available(iOS 8.0, *)
{
//iOS 8.0 and above
presentingController.providesPresentationContextTransitionStyle = true;
presentingController.definesPresentationContext = true;
presentingController.modalPresentationStyle = UIModalPresentationStyle.overCurrentContext
}
else
{
presentingController.modalPresentationStyle = UIModalPresentationStyle.currentContext
selfController.navigationController?.modalPresentationStyle = UIModalPresentationStyle.currentContext
}
}
Set modal presentation style for view controller.
Try this and see (may this would work, programatically that one is not working with storyboard segue.):
func showPopVc(itemId; Int) {
let storyBoard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let PopUpVc = storyBoard.instantiateViewController(withIdentifier: "PopUpVc")
// Try anyone of these according to your requirement.
PopUpVc.modalPresentationStyle = .overCurrentContext
//PopUpVc.modalPresentationStyle = .overFullScreen
self.present(PopupVc, animated: true, completion: nil)
}

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.

Swift / how to call delegate with popViewController

I have read this thread (and similar others) from bottom to top, but it doesn't fit my needs at all.
I have a UIViewController inside UIPageViewController within a UINavigationController. Navigating to a 2nd ViewController. Navigating to a 3rd ViewController and want to pop back to 2nd ViewController delivering data.
My code currently:
protocol PassClubDelegate {
func passClub(passedClub: Club)
}
class My3rdVC: UIViewController {
var clubs: [Club] = []
var passClubDelegate: PassClubDelegate?
....
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let club = clubs[indexPath.row]
self.passClubDelegate?.passClub(club)
navigationController?.popViewControllerAnimated(true)
}
My 2nd VC:
class My2ndVC: UIViewController, PassClubDelegate {
var club = Club()
func passClub(passedClub: Club) {
SpeedLog.print("passClub called \(passedClub)")
club = passedClub
}
passClub is not called. I'm sure it's because I didn't set the delegate to the My2ndVC, but how would I do that? All the solutions I have found wanting me to use a) segue or b) instantiate a My2ndVC new, what doesn't make any sense since it's still in memory and I want to pop back to go back in hierarchy. What am I missing? What are my possibilities? Help is very appreciated.
PS: I'm not using any segues. My3rdVC is called by:
let vc = stb.instantiateViewControllerWithIdentifier("My3rdVC") as! My3rdVC
self.navigationController?.pushViewController(vc, animated: true)
You can set the delegate of My3rdVC in the prepareForSegue method of My2ndVC.
class My2ndVC: UIViewController, PassClubDelegate {
...
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
super.prepareForSegue(segue, sender: sender)
switch segue.destinationController {
case let controller as My3rdVC:
controller.passClubDelegate = self
}
}
}
This is assuming you have created a segue in your storyboard that pushes My3rdVC from My2ndVC onto the navigation controller stack, which I'm assuming you have. So just try simply pasting this prepareForSegue method into My2ndVC and see if it works.
UPDATE
let vc = stb.instantiateViewControllerWithIdentifier("My3rdVC") as! My3rdVC
vc.passClubDelegate = self
navigationController?.pushViewController(vc, animated: true)
When pop one VC to another you can pass data using protocol by declare delegate variable as static. Here in the following example we pop SecondVC to FirstVC and we pass a string.
class FirstVC: UIViewController,getDataDelegateProtocol {
#IBOutlet weak var label: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
SecondVC.delegate = self
}
func getData(tempStr: String) {
label.text = tempStr
}
#IBAction func buttonClick(_ sender: UIButton){
let nav = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "SecondVC") as! SecondVC
self.navigationController?.pushViewController(nav, animated: true)
}
}
Another View Controller
protocol getDataDelegateProtocol{
func getData(tempStr: String)
}
class SecondVC: UIViewController {
#IBOutlet weak var label: UILabel!
static var delegate: getDataDelegateProtocol?
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func buttonClick(_ sender: UIButton){
SecondVC.delegate?.getData(tempStr: "Received data from SecondVC")
self.navigationController?.popViewController(animated: true)
}
}

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