I've got next situation: from the viewDidAppear I have to call the specific function ( to change the view controller to another one) when the special condition applied ( static member changes to True, another class access this static property and send True once the certain conditions apply)
If I do it like this with property observer:
//this is property of load view controller
static var isFilled: Bool = false {
didSet{
if isFilled == true {
print("data is Filled")
//load view controller - my present VC, which I want to switch
var loadVC = LoadViewController()
loadVC.changeViewController(vc: loadVC )
This is changeViewController function:
func changeViewController(vc: UIViewController) {
let sb = UIStoryboard(name: "Main", bundle: nil )
//main view controller - controller, which i want to Go to
var mainViewController = sb.instantiateViewController(withIdentifier: "ViewController") as! ViewController
vc.present(mainViewController, animated: true, completion: nil)
}
It'll throw an error attempt to present viewcontroller whose view is not in the window hierarchy
And, it's performed inside LoadViewController class
As I understand, the only way to avoid this error is to call the function from the viewDidApper, which can't be used in this scenario, because i have to call it only when condition applied. Are there any alternatives to perform that w/o using property observer?
I'm sure there are multiple ways to perform this, and there are might be misconceptions from my side. I'm pretty fresh developer, and completely new to Swift. All the suggestions would be much appreciated.
You could use NotifiationCenter by registering a notification and then calling it whenever you want to update something, like "updateConversations" in a chat app. For example in viewDidLoad(), register your notification:
NotificationCenter.default.addObserver(self, selector: #selector(self.updateConversations(notification:)), name: NSNotification.Name(rawValue: "updateConversations"), object: nil)
Add this function into the class:
#objc func updateConversations(notification: NSNotification) {
if let id = notification.userInfo?["id"] as? Int,
let message = notification.userInfo?["message"] as? String {
// do stuff
}
}
Use the notification from anywhere in your app:
let info = ["id" : 1234, "message" : "I almost went outside today."]
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "updateConversationsList"), object: self, userInfo: info)
Related
This question already has answers here:
Passing data between view controllers
(45 answers)
Closed 2 years ago.
I am creating a library in IOS/swift that:
takes a user to a scene --> performs a task --> return to the initial scene that called the first while passing a payload back to the user
I have figured out how to take users back to the previous scene that called it, but my issue is how to send a payload back with it using thee code snippet below:
func switchToPreviousPage(){
self.dismiss(animated: true, completion: nil)
}
How do I achieve this?
In your scenario you can use either :
Delegation Pattern
Notification/Observer
Lets discuss each one :
1. Delegation :
If you have idea about Protocol in Swift you can do it easily.
first create a protocol with the required function you want to implement :
protocol FirstControllerDelegate: AnyObject {
func sendData(data: String)
}
Suppose your firstPage is FirstViewController, it has a UILabel and we have to assign a String to it from our secondPage means SecondViewController. the Structure of your FirstViewController may be like this :
class FirstViewController: UIViewController {
#IBOutlet weak var textLabel: UILabel!
#IBAction func gotoSecondPage() {
let secondVC = storyboard?.instantiateViewController(withIdentifier: "SecondViewController") as! SecondViewController
}
}
Now your FirstViewController has to confirm to this protocol and it will implement the sendData(data: ) method :
extension FirstViewController: FirstControllerDelegate {
func sendData(data: String) {
textLabel.text = data
}
}
Now as a feature of Protocol in iOS, Protocols can work as a Type(like Int, String). So just create a variable of type FirstControllerDelegate in your SecondViewController !
class SecondViewController: UIViewController {
weak var delegate: FirstControllerDelegate!
#IBAction func switchToPreviousPage() {
delegate.sendData(data: "Hello")
self.dismiss(animated: true, completion: nil)
}
}
You can now call the sendData(data:) function with the variable you created above !
At last you have to do oneThing just assign the delegate :
secondVC.delegate = self
It should be inside the gotoSecondPage() method !
2. Notification/Observer
With this, our basic idea is to send a Notification inside our app, and it can be observed by any where inside !
So our SecondViewController will send a Notification embedded with required data that we want to pass, and FirstViewController will receive the Notification and it will extract the data from the Notification !!
Each Notification has a specific name, which will differentiate it from other Notifications. we have to create the Name :
Notification.Name(rawValue: "com.app.notificationObserver")
Now the FirstViewController will be Observe to this specific notification :
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(self.changeLabelText(notifcation:)), name: Notification.Name("com.app.notificationObserver"), object: nil)
}
We have to define changeLabelText(notification:) method :
private func changeLabelTExt(notification: NSNotification) {
if let dataDict = notification.userInfo as NSDictionary? {
if let message = dataDict["data"] as? String {
self.textLabel.text = message
}
}
}
Finally, SecondViewController will trigger the Notification :
#IBAction func switchToPreviousPage() {
NotificationCenter.default.post(name: Notification.Name(rawValue: "com.app.notificationObserver"), object: ["data": "hello"])
self.dismiss(animated: true, completion: nil)
}
Thats All .....
Hello, I am using MMDrawerController for right side menu. I have 2 ViewController First is HomeVC with Product Listing data in UICollectionView and there's 1 filter button.
When i press that filter button I push to filter screen RightViewVC. Now what I want is, I want to pass that selected filter values to HomeVC. How can I do this?
You can do this by multiple way
1. By using Block Method
When you push to RightViewVC write below code.
let nextViewController = self.storyboard?.instantiateViewController(withIdentifier: "RightViewVC") as! RightViewVC
nextViewController.delegate = self as! customeDelegate
nextViewController.onApplyFilterTap = {(_ arrSelectedFilter: NSMutableArray) -> Void in
self.collectionView.reloadData()
}
self.show(nextViewController, sender: self)
Define this in RightViewVC controller. I created array you can change it as per your requirement.
var onApplyFilterTap: ((_ arrSelectedFilter: NSMutableArray) -> Void)? = nil
You need to call like this
self.arrFilterSelection.add(whichButtonClicked)
self.arrFilterSelection.add(locationTextView.text!)
self.arrFilterSelection.add(byPriceToTextField.text!)
self.arrFilterSelection.add(byPriceFromTextField.text!)
self.arrFilterSelection.add(timeTextview.text!)
onApplyFilterTap!(self.arrFilterSelection)
2. By using NotificationCenter
Write below in your HomeVC
NotificationCenter.default.addObserver(self, selector: #selector(refreshProductListBasedonSelectedFilterValue(_:)), name: NSNotification.Name(rawValue: "refreshProductListBasedonSelectedFilterValue"), object: nil)
#objc func refreshProductListBasedonSelectedFilterValue(_ notification: Notification) {
let info = notification.object as? NSDictionary
let arrSelectedFilteredValues = info?.value(forKey: "selectedFilter") as! NSMutableArray
self.collectionView.reloadData()
}
From RightViewVC you need to call like this.
let dict = NSMutableDictionary()
dict.setValue(self.arrFilterSelection, forKey: "selectedFilter")
NotificationCenter.default.post(name: NSNotification.Name("refreshProductListBasedonSelectedFilterValue"), object: dict)
3. You can also use delegate
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)
}
}
Everything I've seen on stack is passing the data from an input, onto another view controller on a button press. Let's say I have var banana that is an array of dictionaries, but once my function in ViewA.swift is done loading up banana, I want another viewController, say ViewB.swift to manipulate that data as it sees fit. I do NOT have a segue going from one view controller to the other.
EDIT: It's actually two TableViewControllers****
I've looked into NSNotificationCenter, but that doesn't seem to work with my variable type, which is an array of dictionaries
Use NSNotificationCenter for accessing data.
Try Below code
//Sent notification
let dictionary = ["key":"value"]
NSNotificationCenter.defaultCenter().postNotificationName("passData", object: nil, userInfo: dictionary)
//Receive notification
NSNotificationCenter.defaultCenter().addObserver(self,
selector:"myMethod:", name: "passData", object: nil)
//Receive notification method
func myMethod(notification: NSNotification){
print("data: \(notification.userInfo!["key"])")
Without using segue, you can instantiate the View controller, and set the public parameteres.
let storyboard = UIStoryboard(name: "MyStoryboardName", bundle: nil)
let vc = storyboard.instantiateViewControllerWithIdentifier("someViewController") as! ViewB
/* Here you have the reference to the view, so you can set the parameters*/
vc.parameterInViewB = banana
/* At this point you can present the view to the user.*/
self.presentViewController(vc, animated: true, completion: nil)
Make sure that you have given all ViewControllers an identifier, then instantiate them with:
guard let viewControllerB = storyboard?.instantiateViewControllerWithIdentifier("ViewControllerB") as? ViewControllerB else {
fatalError(); return
}
// then access the variable/property of ViewControllerB
viewControllerB.banana = whatEver
Added for clarification
This one works for me.
Just make sure that you have given the TableViewController an identifier otherwise you will not be able to instantiate it. Also make sure that you cast the result of instantiateViewControllerWithIdentifier to your TableViewController class otherwise you won't be able to access it's variables (I've seen that you were struggling with this; if you get an error that UIViewController doesn't have a member "myArray" then you probably have forgotten to cast the result)
class TableViewController: UITableViewController {
var myArray = [String]()
}
class ViewController: UIViewController {
func someEventWillTriggerThisFunction() {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
guard let tableViewController = storyboard.instantiateViewControllerWithIdentifier("TableViewController") as? TableViewController else {
fatalError(); return
}
tableViewController.myArray = ["Value1", "Value2", "Value3"]
/* if you want to present the ViewController use this: */
self.presentViewController(tableViewController, animated: true, completion: nil)
}
}
I need to send a username between two view controllers so that the second view controller knows who to send a message to. I have tried prepareForSegue, however I have found that the variable passed cannot be dynamically altered. I decided to use NSUserDefaults, which worked very well for the length of my development process. Today, it stopped working. I do not think I deleted anything or made an changes, but nevertheless NSUserDefaults is no longer reliably carrying the value between the two view controllers. Every once in a while (maybe 20% of the time?) the value will be correctly passed. The rest of the time, nothing comes through.Code:
Set key:
func chooseFriend(sender: UIButton) {
let requestIndex = sender.tag
let friendChosen = self.friends.objectAtIndex(requestIndex) as! String
NSUserDefaults.standardUserDefaults().setValue("thisisatest", forKey: "testKey")
NSUserDefaults.standardUserDefaults().synchronize()
self.performSegueWithIdentifier("toChat", sender: self)
}
Note: In the viewDidLoad I set testKey = ""
Retrieve key on new view controller:
override func viewDidLoad() {
super.viewDidLoad()
let theKey = NSUserDefaults.standardUserDefaults().valueForKey("testKey")
print("The Key: \(theKey)")
refreshTable()
let swipe: UISwipeGestureRecognizer = UISwipeGestureRecognizer(target: self, action: "dismissKeyboard")
swipe.direction = UISwipeGestureRecognizerDirection.Down
self.view.addGestureRecognizer(swipe)
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardWillShow:"), name: UIKeyboardWillShowNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardWillHide:"), name: UIKeyboardWillHideNotification, object: nil)
testLabel.text = ""
self.messages.addObject("Loading......")
}
Every time, the default comes up empty: The Key: Optional()I can successfully print the default after setting it, however it disappears once I am segued to the next view controller...If anyone else has experienced this problem please let me know.Thanks
Randy's code:
func chooseFriend(sender: UIButton) {
let requestIndex = sender.tag
let friendChosen = self.friends.objectAtIndex(requestIndex) as! String
// Instantiate the second view controller via t's identifier in the storyboard
if let secondViewController = self.storyboard?.instantiateViewControllerWithIdentifier("ChatVC") as? chatViewController {
// Set the chosen friend
secondViewController.friendChosen = friendChosen
self.presentViewController(secondViewController, animated: true, completion: nil)
}
}
Added this to destinationviewcontroller:
var friendChosen: String!
The methods for NSUserDefaults are setObject:forKey: and objectForKey:, not setValue:forKey: (Or look at the special methods for specific object types, like setBool:forKey: or stringForKey: (I don't think there's a custom set method for strings.))
The methods with "value" in their names are KVC methods.
But, as Randy says, using your app's model is a better way to go, or passing the information directly to a property in the destination view controller in prepareForSegue. Using NSUserDefaults would not be my first, or my second, choice in this situation.
It looks like you're using storyboards already so it should be pretty easy to pass information using prepareForSegue like this.
class DestinationVC : UIViewController {
var destName : String!
override func viewWillAppear(animated: Bool) {
//configure UI with the destName
self.label.text = destName
}
}
class PresentingVC : UIViewController {
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let destinationVC = segue.destinationViewController as? DestinationVC {
destinationVC.destName = "Some String to Pass"
}
}
}
As already mentioned NSUserDefaults is not ideal. You will also be loosing type safety and relying on string matching with keys in NSUserDefaults rather than autocompleting and compiler checking with a var on the destinationVC. It's also good practice to limit where your data is kept and where it could be altered. Storing something in NSUserDefaults when the use case is quite confined will make it more difficult to write focussed tests and make it vulnerable to change from any class anywhere in the app. It may be an edge case but starting a pattern like this in your app could expose you to all sorts of side effect bugs in the future.
Ultimately, this type of information should be passed from view controller to view controller in a model via a delegate. That would be the "appropriate" way to achieve this behavior via a true MVC pattern.
Having said that; I think the quickest fix for you would be not to use segues and to avoid NSUserDefaults all together.
Try the following...
func chooseFriend(sender: UIButton) {
let requestIndex = sender.tag
let friendChosen = self.friends.objectAtIndex(requestIndex) as! String
// Instantiate the second view controller via it's identifier in the storyboard
if let secondViewController = self.storyboard?.instantiateViewControllerWithIdentifier("SecondViewControllerIdentifier") as? SecondViewController {
// Set the chosen friend
secondViewController.friendChosen = friendChosen
self.presentViewController(secondViewController, animated: true, completion: nil)
}
}
And in the SecondViewController add the following property.
var friendChosen: String!
Please make sure the value is not nil prior to passing it to the destination view controller