Dismiss performed UIViewController within controller that performed segue - ios

I have UINavigationController with root UICalendarViewController. At some time while app running, inside UICalendarViewController I perform segue with UICalendarEventViewController. I keep reference to the 'UICalendarViewController' using
var calendarViewController: UICalendarViewController!
Later at some time I have an access to the UICalendarViewController, and then I need to dismiss UICalendarEventViewController. How to do this?
Inside UICalendarViewController I created
var calendarEventViewController: UICalendarEventViewController?
But when I try to dismiss it using:
calendarViewController.calendarEventViewController.dismissViewController(true, animated: true)
it doesn't work.

Sometimes for some reason we do not have the same instance of controller what we expect. In my case I didn't get the same instance of UICalendarViewController. But there is a way to do this if as me you use UITabBarController as a main navigation:
if let navigationController = viewControllersByIdentifier[PLContainerTabBarItemCalendar] as? UINavigationController {
navigationController.popToRootViewControllerAnimated(true)
}
Then I have a current controller, and I can manage them with visible results.

Related

Dismiss Two View Controllers and then Push a View Controller - Swift

I have 4 view controllers I am dealing with in this question. For simplictiy sake I will refer to them as vcA, vcB, vcC, and vcD.
Overall what I want to do is dismiss two view controllers and then push the third view controller to the 4th view controller. Essentially this is how the path would go: vcA(dismissed) -> vcB(dismissed) -> vcC pushes to vcD(final destination).
Here is my current code for the process. Please note I am able to dismiss the first two view controllers but I can't figure out how to push the third view controller. Anyways here is my code:
CODE FOR vcA:
#IBAction func createGroupButtonTapped(_ sender: Any) {
if groupNameTextField.text == "" {
ProgressHUD.showError("Please enter a group title")
} else {
self.presentingViewController?.dismiss(animated: false, completion: nil)
NewConversationController().showDisplayConversationsController(title: self.groupNameTextField.text!, groupThumbnail: self.groupImageView, members: CreateConversationPopOver.groupMembers)
self.presentingViewController?.dismiss(animated: true, completion: nil)
}
}
In the code above I dismiss the first two view controllers and also call a function in vcB. This function in vcB calls a function in vcC. Here is the code for the functions:
FUNCTION IN vcB:
func showDisplayConversationsController(title: String, groupThumbnail: UIImageView, members: Array<String>) {
dismiss(animated: true, completion: nil)
DisplayConversationsController().showViewChatController(title: title, groupThumbnail: groupThumbnail, members: members)
}
FUNCTION IN vcC:
func showViewChatController(title: String, groupThumbnail: UIImageView, members: Array<String>) {
let viewChatController = ViewChatController(collectionViewLayout: UICollectionViewFlowLayout())
viewChatController.hidesBottomBarWhenPushed = true
viewChatController.groupThumbnail = groupThumbnail
viewChatController.groupMembers = members
viewChatController.groupTitle = title
navigationController?.pushViewController(viewChatController, animated: true)
}
The problem occurs in VCC where the view controller never pushes to the final destination otherwise known as vcD. I have added breakpoints within the function above and they are all called. So the code is being run.
I don't know why the view controller won't push because even after numerous testing, all my functions are being called.
Any help would be appreciated. Thanks!
PS: In case you are wondering why I want to dismiss views and present them this way, it is because I need to pass data through these views into a chat log controller.
first.
check vcC's navigationController, it may be nil.
It seems you call Class methods NewConversationController DisplayConversationsController, (not familiar with swift, or these two are ) not instance methods.
Maybe it not the right vcC, just a new vcC?
PS: It is quite strange that you vcA.presentingViewController dismiss twice and according to what you say, It just dismiss fine.
if you can describe how you present vcA, vcB or give a demo to show how it work, I think it may be helpful.
You can use Notification Pattern for the same.
If you want to switch directly from vcA to VcD, fire notification in vcA before switching to vcD. Make vcB, vcC as listener to the same notification. When u directly switch from vcA to vcD, vcB and vcC will get notified if they already present. Handle the notification in vcB and vcC and hence dismissViewController or PopViewController as required. You can even pass data through from vcA directly to vcD using notification.
You can't push a view controller into a view dismissed, if it's dismissed it disappears so it's not logical to push another view, cause the parent view controller is deleted.
What you have to do is - vcA --> Present vcB --> Dismiss vcB --> Present vcC Push vcD on vcC > Dismiss vcC
You can do it with notifications. You can even accomplish the task using delegation pattern. Make vcB as delegate of vcC that notifies vcB when vcB dismisses then dismiss vcB and then just push vcD on vcC.
Hope it helps. Happy Coding!!

Pass data between three viewController, all in navigationController, popToRootView

The issue I'm having is this.
I have a navigation controller with 3 viewController. In the 1st controller, I have the user select an image. This image is passed to 2nd and 3rd controller via prepareForSegue.
At the 3rd controller, I have a button that takes the user back to the 1st view controller. I explored 2 ways in doing this:
1) use performSegue, but I don't like this because it just push the 1st controller to my navigation stack. So I have this weird "Back" button at the 1st Viewcontroller now, which is not what I want. I want the app to take user directly to 1st viewcontroller without the back button.
2) I tried Poptorootviewcontroller. This solves the issue of the "back" button. But, when I pop back to the 1st viewcontroller, the user's selected image is still on screen. I want to clear this image when the user goes from the 3rd viewcontroller back to the 1st viewcontroller.
So with approach 2), how do I make sure all memory is refreshed and the image becomes nil in the 1st viewcontroller? Since I'm not using performSegue, 3rd viewcontroller does not have access to the 1st Viewcontroller.
For refresh, you'd have to clear it in viewWillAppear but I find this rather dangerous. Best you can do there is to create a new copy of the view controller everytime and Swift will take care of the rest. I don't know if you are using the storyboard but I would recommend using the class UIStoryboard and the function instiantiateViewControllerWithIdentifier("something") as! YourCustomVC
As long as you stay in the navigation stack, you'll not lose any of the current configurations of previous View Controllers.
As for passing data back to the first controller. You can either just throw it in the global scope which is the easiest way but might be difficult to know when it was updated or if the data is fresh. But you can always just:
var something: String = ""
class someView: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
something = "foo"
}
}
Something will be availabe everywhere then.
You could make a protocol and pass the delegate along the 3 view controllers. So when you are starting it you could do:
func someAction() {
let v = SomeViewController()
v.delegate = self
self.navigationController?.pushViewController(v, animated: true)
}
And then with each following view:
func someOtherAction() {
let v = SomeOtherViewController()
v.delegate = self.delegate
self.navigationController?.pushViewController(v, animated: true)
}
Although personally I find it hard to keep track of this.
Lastly you could use the NSNotificationCenter to pass an object along with all the data and catch it in a function on your first controller.
To do this you first register your VC for the action in viewDidLoad() or something:
NSNotificationCenter.defaultCenter().addObserver(self, selector: "someAction:", name: "someNotification", object: nil)
Then when you are done in the 3rd view make some object or a collection of string and send it back as follows:
NSNotificationCenter.defaultCenter().postNotificationName("someNotification", object: CustomObject())
And then lastly you'll catch it in the function "someAction"
func someAction(note: NSNotification) {
if let object = note.object as? CustomObject {
//Do something with it
}
}
Hope this helps!
Use an unwind segue which provides the functionality to unwind from the 3rd to the 1st (root) view controller.
The unwind segue is tied to an action in the root view controller. Within this action, you simply nil the image:
#IBAction func unwindToRootViewController(sender: UIStoryboardSegue)
{
let sourceViewController = sender.sourceViewController
// Pull any data from the view controller which initiated the unwind segue.
// Nil the selected image
myImageView.image = nil
}
As you can see in the action, segues also let you pass data back from the source view controller. This is a much simpler approach than needing to resort to using delegates, notifications, or global variables.
It also helps keep things encapsulated, as the third view controller should never need to know specifics about a parent view controller, or try to nil any image that belongs to another view controller.
In general, you pass details to a controller, which then acts on it itself, instead of trying to manipulate another controller's internals.

Why does my ViewController still reload every time I segue to it?

The header of my HomeViewController looks like this:
private let sharedGameBoard = HomeViewController()
class HomeViewController: UIViewController, CLLocationManagerDelegate, UITextFieldDelegate {
class var sharedInstance: HomeViewController {
return self.sharedInstance
}
I have my HomeViewController embedded in a UINavigationController. To navigate to a MultipleChoiceViewController I call:
var multipleChoiceVC: MultipleChoiceViewController = self.storyboard?.instantiateViewControllerWithIdentifier("multipleChoiceViewController") as! MultipleChoiceViewController
self.navigationController?.pushViewController(multipleChoiceVC, animated: true)
To return from MultipleChoiceViewController I call a show segue that is hooked up to a swipe gesture in my main.storyboard file.
Every time I navigate from MultipleChoiceViewController to HomeViewController, HomeViewController completely reloads. How do I keep this from happening. Like is seen in so many applications, I would like to be able to navigate from detail views back to the home controller without the home controller loading each time.
My load functions are called in viewDidLoad but I have a global bool variable that indicates whether the load functions should be called or not.
I've spent a good deal of time trying to understand singletons, push/show segues, and the navigation stack, to try and alleviate what is probably a very simple thing to accomplish. What is a way to implement this?

Set initial viewController in Navigation controller programmatically (Layer SDK)

I want to add Layer SDK to my application (using Swift).
All view controllers here are created programmatically. Therefore I can't segue to them. I have 4 tabs in my application (UITabBarController). One of them is chat. In the chat tab I created a segue to UINavigationController. Now I want to load conversationListViewController in this UINavigationController. For that I created a class for this UINavigationController i.e. ConversationListViewController and added the following code:
class ChatNavigationViewController: UINavigationController {
var conversationListViewController: ConversationListViewController!
var layerClient: LYRClient!
override func viewDidLoad() {
let appDelegate = UIApplication.sharedApplication().delegate as!AppDelegate
self.layerClient = appDelegate.layerClient
self.conversationListViewController = ConversationListViewController(layerClient: appDelegate.layerClient)
self.conversationListViewController.displaysAvatarItem = true
self.navigationController!.pushViewController(self.conversationListViewController, animated: true)
}
}
But this is not working. And giving this kind of effect: the ConversationViewController is not loaded in UINavigationController. I am not sure if I am doing it the correct way. I'm searching for the correct way, but unable to find.
I Solved it. I dragged new NavigationViewController and added ConversationListViewController to rootviewController.I think i should try this first. Anyways thanks guys for your help.
Because you want to do this programatically:
You need to manually initialize the controller before stacking it up on the Navigation Controller. Try this:
navigationController?.pushViewController(self.conversationListViewController.init(), animated: true)

Pass data to view controller before initializer when using segues. (Swift)

I'm new to swift and am trying to write an app with it.
I have a UIViewController that I am transitioning to. I have designed the UI in interface builder and I intend to use segues to manage the transition. However, the view controller relies on data that is passed into the view controller from the previous view controller.
If I have properties on my view controller then I will need to redefine my init method. But I wouldn't normally call the init method; it would be called for me before prepareForSegue. So I see a few possible solutions:
Make my variables optional (so I can pass them in prepareForSegue
and update the view then).
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let viewController: EventViewController = segue.destinationViewController as! EventViewController
viewController.event = self.event
}
Manually init my view controller and present it programmatically instead.
???
Is there a third option? If not, which of the previously mentioned 2 is better practice?
There is two possible options as you mentioned:
The first one is the easiest which is to pass the data in prepareForSegue. which you don't have to care about dismissing the controller or keeping a track of inner view controllers,because storyboard will take care of it.
The second way is to set a Storyboard ID in storyboard,for the controller you need to present programmatically, which need more things to handle, like to dismiss the controller or keep track of inner presented controllers.
let nextViewControllerName = storyboard?.instantiateViewControllerWithIdentifier("Storyboard ID") as! nextViewControllerName
nextViewControllerName.event = self.event
self.presentViewController(nextViewControllerName, animated: true, completion: nil).
At the end they does the same purpose.
Note: You should always pass the data before presenting the controller.

Resources