Reloading TableView when a UIViewController is being dismissed? - ios

The problem here is that I'm presenting EditCommentVC modally, over the current context of the CommentVC because I want to set the background of the UIView to semi-transparent. Now, on the EditCommentVC I have a UITextView that allows the user to edit their comment, along with 2 buttons - cancel (dismisses the EditCommentVC) and update that updates the new comment and push it to the database.
In term of code, everything is working, except that once the new comment is being pushed and EditCommentVC is being dismissed, the UITableView on CommentsVC with all the comments is not being reloaded to show the updated comments. Tried calling it from viewWillAppear() but it doesn't work.
How can I reload the data in the UITableView in this case?
#IBAction func updateTapped(_ sender: UIButton) {
guard let id = commentId else { return }
Api.Comment.updateComment(forCommentId: id, updatedComment: editTextView.text!, onSuccess: {
DispatchQueue.main.async {
let commentVC = CommentVC()
commentVC.tableView.reloadData()
self.dismiss(animated: true, completion: nil)
}
}, onError: { error in
SVProgressHUD.showError(withStatus: error)
})
}
The code in the CommentVC where it transitions (and passes the id of the comment). CommentVC conforms to a CommentActionProtocol that passes the id of that comment:
extension CommentVC: CommentActionProtocol {
func presentActionSheet(for commentId: String) {
let actionSheet = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
let editAction = UIAlertAction(title: "Edit", style: .default) { _ in
self.performSegue(withIdentifier: "CommentVCToEditComment", sender: commentId)
}
actionSheet.addAction(editAction)
present(actionSheet, animated: true, completion: nil)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "CommentVCToEditComment" {
let editCommentVC = segue.destination as! EditCommentVC
let commentId = sender as! String
editCommentVC.commentId = commentId
}
}
}

I see atleast 2 problems here:
You are creating a new CommentVC which you should not do if you want to update the tableView in the existing view controller.
Since you have mentioned that Api.Comment.updateComment is a an asynchronous call, you need to write the UI code to run on the main thread.
So first you need to have the instance of the commentVC in a variable inside this viewController. You can store the instance of the view controller from where you are presenting this view controller.
class EditCommentVC {
var commentVCdelegate: CommentVC!
// Rest of your code
}
Now you need to pass the reference commentVC in this variable when you are presenting the edit view controller.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "CommentVCToEditComment" {
let editCommentVC = segue.destination as! EditCommentVC
let commentId = sender as! String
editCommentVC.commentId = commentId
editCommentVC.commentVCdelegate = self
}
}
Now you need to use this reference to reload your tableView.
Api.Comment.updateComment(forCommentId: id, updatedComment: editTextView.text!, onSuccess: {
DispatchQueue.main.async {
commentVCdelegate.tableView.reloadData() // - this commentVC must be an instance that you store of the your commentVC that you created the first time
self.dismiss(animated: true, completion: nil)
}
}, onError: { error in
SVProgressHUD.showError(withStatus: error)
})

Well, i had this problem too, and the solution i found was to use Protocol. I would recommend you to search how to send data back to previous ViewController. That way, when you dismiss the EditCommentVC, you then send back a value(in my case i send true) to the previous ViewController(in your case, CommentVC), and then you'll have a function in CommentVC checking if the value is true and if it is, reload the TableView.
Here, let me show you an example of how i used (those are the names of my ViewControllers, functions and protocols, you can use whatever you want and send whatever data you want back):
In your CommentVC, you'll have something like this:
protocol esconderBlurProtocol {
func isEsconder(value: Bool)
}
class PalestranteVC: UIViewController,esconderBlurProtocol {
func isEsconder(value: Bool) {
if(value){
//here is where you can call your api again if you want and reload the data
tableView.reloadData()
}
}
}
Also, dont forget that you have to set the delegate of EditCommentVC, so do it when you're presenting EditCommentVC, like this:
let viewController = (self.storyboard?.instantiateViewController(withIdentifier: "DetalhePalestranteVC")) as! DetalhePalestranteVC
viewController.modalPresentationStyle = .overFullScreen
viewController.delegate = self
self.present(viewController, animated: true, completion: nil)
//replace **DetalhePalestranteVC** with your **EditCommentVC**
And in your EditCommentVC you'll have something like this:
class DetalhePalestranteVC: UIViewController {
var delegate: esconderBlurProtocol?
override func viewWillDisappear(_ animated: Bool) {
delegate?.isEsconder(value: true)
}
}
That way, everything you dismiss EditCommentVC, you'll send back True and reload the tableView.

Related

How to execute a segue in Swift from a button click or 'return' key entered? [duplicate]

I'm creating an app that uses the Facebook SDK to authenticate users. I'm trying to consolidate the facebook logic in a separate class. Here is the code (stripped for simplicity):
import Foundation
class FBManager {
class func fbSessionStateChane(fbSession:FBSession!, fbSessionState:FBSessionState, error:NSError?){
//... handling all session states
FBRequestConnection.startForMeWithCompletionHandler { (conn: FBRequestConnection!, result: AnyObject!, error: NSError!) -> Void in
println("Logged in user: \n\(result)");
let storyboard = UIStoryboard(name: "Main", bundle: NSBundle.mainBundle())
let loggedInView: UserViewController = storyboard.instantiateViewControllerWithIdentifier("loggedInView") as UserViewController
loggedInView.result = result;
//todo: segue to the next view???
}
}
}
I'm using the above class method to check session state changes, and it works fine.
Q: Once I have the user's data, how can I segue to the next view from within this custom class?
Just to be clear, I have a segue with identifier on the storyboard, and I'm trying to find a way to perform a segue from a class which is not the view controller
If your segue exists in the storyboard with a segue identifier between your two views, you can just call it programmatically using:
performSegue(withIdentifier: "mySegueID", sender: nil)
For older versions:
performSegueWithIdentifier("mySegueID", sender: nil)
You could also do:
presentViewController(nextViewController, animated: true, completion: nil)
Or if you are in a Navigation controller:
self.navigationController?.pushViewController(nextViewController, animated: true)
If your segue exists in the storyboard with a segue identifier between your two views, you can just call it programmatically using
self.performSegueWithIdentifier("yourIdentifierInStoryboard", sender: self)
If you are in Navigation controller
let viewController = YourViewController(nibName: "YourViewController", bundle: nil)
self.navigationController?.pushViewController(viewController, animated: true)
I will recommend you for second approach using navigation controller.
You can use NSNotification
Add a post method in your custom class:
NSNotificationCenter.defaultCenter().postNotificationName("NotificationIdentifier", object: nil)
Add an observer in your ViewController:
NSNotificationCenter.defaultCenter().addObserver(self, selector: "methodOFReceivedNotication:", name:"NotificationIdentifier", object: nil)
Add function in you ViewController:
func methodOFReceivedNotication(notification: NSNotification){
self.performSegueWithIdentifier("yourIdentifierInStoryboard", sender: self)
}
You can use segue like this:
self.performSegueWithIdentifier("push", sender: self)
override func prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject!) {
if segue.identifier == "push" {
}
}
Swift 3 - Also works with SpriteKit
You can use NSNotification.
Example:
1.) Create a segue in the storyboard and name the identifier "segue"
2.) Create a function in the ViewController you are segueing from.
func goToDifferentView() {
self.performSegue(withIdentifier: "segue", sender: self)
}
3.) In the ViewDidLoad() of your ViewController you are segueing from create the observer.
NotificationCenter.default.addObserver(self, selector: #selector(goToDifferentView), name: "segue" as NSNotification.Name, object: nil)
Update -
Last time I used this I had to change the .addObserver call to the following code to silence the errors.
NotificationCenter.default.addObserver(self, selector: #selector(goToDifferentView), name: NSNotification.Name(rawValue: "segue"), object: nil)
4.) In the ViewController or Scene you are segueing to, add the Post Method wherever you want the segue to be triggered.
NotificationCenter.default.post(name: "segue" as NSNotification.Name, object: nil)
Update -
Last time I used this I had to change the .post call to the following code to silence the errors.
NotificationCenter.default.post(NSNotification(name: NSNotification.Name(rawValue: "segue"), object: nil) as Notification)
There are already great answers above, i'd like to put little focus on preparation before performing segue.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.destination is YourDestinationVC {
let vc = segue.destination as? YourDestinationVC
// "label" and "friends" are part of destinationVC
vc?.label = "someText"
vc?.friends = ["John","Mike","Garry"]
}
Once your are done with data that you want to pass on to your destinationVC then perform your segue at an appropriate place.
You can set "IdentifierOfDestinationVC" in StoryBoard Identity inspector in StoryBoard ID field
performSegue(withIdentifier: "IdentifierOfDestinationVC", sender: nil)
What you want to do is really important for unit testing. Basically you need to create a small local function in the view controller. Name the function anything, just include the performSegueWithIndentifier.
func localFunc() {
println("we asked you to do it")
performSegueWithIdentifier("doIt", sender: self)
}
Next change your utility class FBManager to include an initializer that takes an argument of a function and a variable to hold the ViewController's function that performs the segue.
public class UtilClass {
var yourFunction : () -> ()
init (someFunction: () -> ()) {
self.yourFunction = someFunction
println("initialized UtilClass")
}
public convenience init() {
func dummyLog () -> () {
println("no action passed")
}
self.init(dummyLog)
}
public func doThatThing() -> () {
// the facebook login function
println("now execute passed function")
self.yourFunction()
println("did that thing")
}
}
(The convenience init allows you to use this in unit testing without executing the segue.)
Finally, where you have //todo: segue to the next view???, put something along the lines of:
self.yourFunction()
In your unit tests, you can simply invoke it as:
let f = UtilClass()
f.doThatThing()
where doThatThing is your fbsessionstatechange and UtilClass is FBManager.
For your actual code, just pass localFunc (no parenthesis) to the FBManager class.
This worked for me.
First of all give the view controller in your storyboard a Storyboard ID inside the identity inspector. Then use the following example code (ensuring the class, storyboard name and story board ID match those that you are using):
let viewController:
UIViewController = UIStoryboard(
name: "Main", bundle: nil
).instantiateViewControllerWithIdentifier("ViewController") as UIViewController
// .instantiatViewControllerWithIdentifier() returns AnyObject!
// this must be downcast to utilize it
self.presentViewController(viewController, animated: false, completion: nil)
For more details see http://sketchytech.blogspot.com/2012/11/instantiate-view-controller-using.html
best wishes
Another option is to use modal segue
STEP 1: Go to the storyboard, and give the View Controller a Storyboard ID. You can find where to change the storyboard ID in the Identity Inspector on the right.
Lets call the storyboard ID ModalViewController
STEP 2: Open up the 'sender' view controller (let's call it ViewController) and add this code to it
public class ViewController {
override func viewDidLoad() {
showModalView()
}
func showModalView() {
if let mvc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "ModalViewController") as? ModalViewController {
self.present(mvc, animated: true, completion: nil)
}
}
}
Note that the View Controller we want to open is also called ModalViewController
STEP 3: To close ModalViewController, add this to it
public class ModalViewController {
#IBAction func closeThisViewController(_ sender: Any?) {
self.presentingViewController?.dismiss(animated: true, completion: nil)
}
}
You can do this thing using performSegueWithIdentifier function.
Syntax :
func performSegueWithIdentifier(identifier: String, sender: AnyObject?)
Example :
performSegueWithIdentifier("homeScreenVC", sender: nil)
This worked for me:
//Button method example
#IBAction func LogOutPressed(_ sender: UIBarButtonItem) {
do {
try Auth.auth().signOut()
navigationController?.popToRootViewController(animated: true)
} catch let signOutError as NSError {
print ("Error signing out: %#", signOutError)
}
}

SWIFT 4 Present viewcontroller

I am trying to building a app using the master details template.
in the Master view controller I added a button called Catalogue : this button showing a tabbar controller called Catalogue.
I don't use Segue to show the catalogue, I use the code below to show the tab controller
From Master form I called the Tabbar controller :
#IBAction func Btn_Catalogue(_ sender: Any) {
let AddCatalogueVC = self.storyboard?.instantiateViewController(withIdentifier: "CatalogueVC") as! CatalogueVC
present(AddCatalogueVC, animated: true, completion: nil)
}
From CategorieVC I use the code below to show
#IBAction func Btn_AddCategorie(_ sender: Any) {
self.Mode = "New"
let AddCategorieViewController = self.storyboard?.instantiateViewController(withIdentifier: "AddCategorieVC") as! AddCategorieVC
present(AddCategorieViewController, animated: true, completion: nil)
}
I dismiss the AddCategorieVC using the code below
#IBAction func Btn_Save(_ sender: Any)
{
if self.Txt_CategorieName.text != ""{
self.Mysave = true
self.MyCategorieName = self.Txt_CategorieName.text!
self.dismiss(animated: true, completion: nil)
}
}
I have unwind SEGUE from Save button to a function in categorieVC
#IBAction func FctSaveCategories(_ sender: UIStoryboardSegue) {
let sendervc = sender.source as! AddCategorieVC
if self.Mode == "New" && sendervc.Mysave == true { // Mode insert
let MyCategories = TblCategorie(context: Mycontext)
MyCategories.categorie_Name = sendervc.MyCategorieName
do {
try Mycontext.save()
} catch {
debugPrint ("there is an error \(error.localizedDescription)")
}
}
}
The problem is when I hit the save button in categorieVC the catalogueVC is also dismissing at the same time returning me to the master control.
I am almost sure that the problem came from the Unwind segue but I don't know why.
Update: I activate the Cancel button in AddCategorieVC with the code below
self.dismiss(animated: true, completion: nil)
and when I clicked on it only the AddCategorieVC is being dismissed and I go back to CatalogueVC. The difference between the save button and the Cancel Button is only the UNWIND segue on the Save Button.
And when I add UnWIND segue to the cancel Button (just to test the behavior) it took me back to the master form instead CatalogueVC.
How can I solve that?
And yesss I found it
It look like that unwind segue automaticly handled dismiss contrĂ´le
So all I need to do is remove the dismiss code from the save button this way the unwind segue will took me back to catalogueVC.
.

How to push user to ViewController from non UIView class [duplicate]

This question already has answers here:
How to launch a ViewController from a Non ViewController class?
(7 answers)
Closed 5 years ago.
I would like to know how can I push user back to specific ViewController from regular swift class without being non UIView Class
Example
class nonUI {
function Push() {
//How to send user back to specific VC here?
}
}
This is a generic method you can use with in the class or outside the class for push if required else it will pop if the instance of view controller is in the stack:
func pushIfRequired(className:AnyClass) {
if (UIViewController.self != className) {
print("Your pushed class must be child of UIViewController")
return
}
let storyboard : UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
var isPopDone = false
let mainNavigation = UIApplication.shared.delegate?.window??.rootViewController as? UINavigationController
let viewControllers = mainNavigation!.viewControllers
for vc in viewControllers {
if (type(of: vc) == className) {
mainNavigation?.popToViewController(vc, animated: true)
isPopDone = true
break
}
}
if isPopDone == false{
let instanceSignUp = storyboard.instantiateViewController(withIdentifier: NSStringFromClass(className)) // Identifier must be same name as class
mainNavigation?.pushViewController(instanceSignUp, animated: true)
}
}
USES
pushIfRequired(className: SignupVC.self)
You could also utilise the NotificationCenter to achieve a loosely coupled way to "request a view controller"; if you will.
For example, create a custom UINavigationController that observes for the custom Notification and upon receiving one, looks for the requested UIViewController and pops back to it.
class MyNavigationController : UINavigationController {
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(forName: NSNotification.Name("RequestViewController"), object: nil, queue: OperationQueue.main) { [unowned self] (note) in
guard let targetType = note.object as? UIViewController.Type else {
print("Please provide the type of the VC to display as an `object` for the notification")
return
}
// Find the first VC of the requested type
if let targetVC = self.viewControllers.first(where: { $0.isMember(of: targetType) }) {
self.popToViewController(targetVC, animated: true)
}
else {
// do what needs to be done? Maybe instantiate a new object and push it?
}
}
}
}
Then in the object you want to go back to a specific ViewController, post the notification.
#IBAction func showViewController(_ sender: Any) {
NotificationCenter.default.post(Notification(name: NSNotification.Name("RequestViewController"), object: ViewController2.self))
}
Now, it's also fairly easy to adopt this method for other presentation-styles.
Instead of using the NotificationCenter, you could also muster up a Mediator to achieve the loose coupling.
You can't. UIViewController and its subclass only can handle navigate between screen.
In your case, need pass link (variable) to navigation controller in custom class.
Like:
class nonUI {
var navigationController: UINavigationController?
init(navigationController: UINavigationController) {
self.navigationController = navigationController
}
function Push() {
//How to send user back to specific VC here?
navigationController?.popViewController(animated: true)
}
}

Swift - Passing data coming from an API to another view controller

Please check the below code:
#IBAction func sendActivationCode(_ sender: UIButton) {
service.Register(phoneNumber: self.mobileNumberTxt.text!, callback: { (response) in
self.setCustomerValues(response: response)
})
}
func setCustomerValues(response: [String:Any]) {
registrationToken = (response["token"]! as! String)
code = response["code"] as! Int
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "toStep2" {
let vc = segue.destination as! Step2ViewController
vc.registrationToken = registrationToken
}
}
The problem is: prepare function is executed before setCustomerValues and I cannot use registrationToken variable in Step2ViewController.swift because it's nil.
Instead of connecting your segue from the button to Step2ViewController, connect it from the view controller. This way the segue will not automatically be performed when the button is touched.
Then call performSegue from within your setCustomerValues callback to perform the segue explicitly after getting the registration token. Note that if the callback is not on the main thread, you will need to dispatch_async to the main thread before calling performSegue.
You should push viewcontroller after self.setCustomerValues(response: response). Don't push viewcontroller when sendActivationCode
The best way to come out of this problem is to create an IBAction method from your button on a Touch Up Inside Event and not create any Segues on 'action' of your button.
Use the following code:
#IBAction func sendActivationCode(_ sender: UIButton) {
service.Register(phoneNumber: self.mobileNumberTxt.text!, callback: {
(response) in
let vc = self.storyboard?.instantiateViewControllerWithIdentifier("Step2ViewController") as! Step2ViewController
vc.registrationToken = (response["token"]! as! String)
vc.code = response["code"] as! Int
self.navigationController?.pushViewController(vc!, animated: true)
})
}

How to Transfer Data when dismissing controller view in Swift

I am working on an iOS app that logs the user in by a Login View.
There are two controllers: LognViewController and SignUpViewController
If a new user signs up instead, then the Sign Up View Controller makes an API call to retrieve a new user account. Then, the Sign Up page should transfer the new User object back to the Login page, which in turn logs the user in to the main app.
Based on a previous post I like the idea of a closure, and I'm trying to implement it here; however, I'm getting a nil on the closure function variable. My code is something like this:
In the First Controller:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "userSignUp" {
if !self.userTextField.text!.isEmpty {
let nav = segue.destinationViewController as! UINavigationController
let vc = nav.topViewController as! SignUpViewController
vc.email = self.userTextField.text!
vc.submitUser = signUpToLogIn
}
}
}
// asynchronous get user back from sign up
func signUpToLogIn(currentUser: User) {
self.currentUser = currentUser
self.checkCurrentUser()
}
In the Second Controller:
var submitUser: ((currentUser: User) -> ())!
#IBAction func signUpButtonTapped(sender: UIButton) {
doSignUp({(u: User?) -> () in
if u != nil {
self.submitUser(currentUser: u!)
self.dismissViewControllerAnimated(false, completion: nil)
}
})
}
I'm looking at the debugger, and it says fatal error: unexpectedly found nil while unwrapping an Optional value When I work with a breakpoint, I can see in the variables section that the submitUser variable is always nil.
Is there a different way of doing this now?
Instead of passing a closure, can you use delegation instead? Your SignUpViewController can notify your LoginViewController when a new user has signed up and pass the new User object back through a delegate method like so
Signup View Controller:
protocol SignUpDelegate {
func userDidSignUp(u: User)
}
class SignUpViewController: UIViewController {
var delegate: SignUpDelegate?
#IBAction func signUpButtonTapped(sender: UIButton) {
// Make async call to sign new user up here
// Once you get a User back from your API call your delegate
// method in your completion or at the end of your network call
self.delegate?.userDidSignUp(newUserObject)
self.dismissViewControllerAnimated(false, completion: nil)
}
}
Then in your login view controller you can implement this delegate method
Login view controller:
class LoginViewController: UIViewController {
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "userSignUp" {
if !self.userTextField.text!.isEmpty {
let nav = segue.destinationViewController as! UINavigationController
let vc = nav.topViewController as! SignUpViewController
vc.email = self.userTextField.text!
vc.delegate = self
}
}
}
// MARK: SignUpDelegate
func userDidSignUp(u: User) {
// Log user in with new user
}
}
Inside the #IBAction func signUpButtonTapped(sender: UIButton):
Try using this function to do the transition to another view controller:
[self performSegueWithIdentifier:#"segueIdentifierHere" sender:self];
Don't forget to remove the self.dismissViewController function you are currently using before you try the performSegueWithIdentifier

Resources