WatchOS: Pass data from one controller to another through "Next Page" segue - swift

I am loading 3 interfacecontrollers with WKInterfaceController.reloadRootControllers with contexts in my app.
Everything works just fine. However, I need to pass data from Page 1 to Page 2 on swipe. It seems there is not segue that I can use programmatically to create a new context or pass data another way.
How could I solve this?
I don't want to create a singleton just for this. I know how to pass data in contexts with other segues, this question specifically relate to "Next Page" navigation on Watchkit. I couldn't find an answer so far.
Thanks!
Markus

Have you tried using contextForSegue in your first page?
override func contextForSegue(withIdentifier segueIdentifier: String) -> Any? {
let myDate = "today"
return myDate
}

I'm been looking around for an answer for this question also..
NextPage Segue is similar to UIPageViewController in iOS. I have tried contextForSegue(withIdentifier segueIdentifier: String) but it is not getting called on swipe, and you can't give this segue an identifier in the storyboard.
The only way around it for me was to use NotificationCenter
In the first InterfaceController I post a notification with the object on willDisappear/didDeactivate doesn't really make a difference with watchOS and on the second InterfaceController I subscribed to it.
NotificationCenter.default.post(name: Notification.Name("interface1WillDisappear"), object: context)
NotificationCenter.default.addObserver(self, selector: #selector(awake(withNotification:)), name: Notification.Name("interface1WillDisappear"), object: nil)
Add I added this func
func awake(withNotification notification: Notification) {
guard let context = context as? MyContextObject else { return }
print(context)
}
Finally don't forget to remove the observer
deinit {
NotificationCenter.default.removeObserver(self)
}
Hope this helps!

Related

How to determine whether current view controller is active, and execute code if active

In my app, there is a ViewController.swift file and a popupViewController.swift file. Inside the app, when I open the popupViewController with storyboard segue as presentModally and then come back from popupViewController to ViewController with the code dismiss(), the methods viewDidLoad, viewWillAppear, viewDidAppear, ViewWillLayoutSubviews etc. nothing works, they execute just once and don't repeat when I go and return back. So, I want to execute the code every time when viewController.swift is active. I couldn't find a useful info in stackoverflow about this.
Meanwhile, I don't know much about notification and observers(if certainly needed), therefore, can you tell step by step in detail how to do that in Swift (not objective-c)? I mean how to determine if current view controller is active.
Edit: I am navigating from StoryBoard segue, presentModally. There is no Navigation Controller in storyboard.
I tried some codes but nothing happens. The point I came so far is:
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector:#selector(appWillEnterForeground), name:UIApplication.willEnterForegroundNotification, object: nil)
}
#objc func appWillEnterForeground() {
print("asdad") //nothing happens
if self.viewIfLoaded?.window != nil {
// viewController is visible
print("CURRENT VİEW CONTROLLER") //nothing happens
}
}
As mention in my comments, I don't use storyboards. There may be a way to create an unwind segue - or maybe not - but [here's a link][1] that may help you with a storyboard-only way of fixing your issue. A quick search on "modal" turned up 9 hits, and the second one starts going into details.
I'm thinking the issue is with what modality is. Basically, your first view controller, which properly executed viewDidAppear, is still visible. So it's effectively not executing viewDidDisappear when your second VC is presented.
You might want to change your concept a bit - an application window (think AppDelegate and/or SceneDelegate become active, where a UIViewController has a is initialized and deinitialized, along with a root UIView that is loaded, appears* and disappears*. This is important, because what you want to do is send your notification from the modal VC's viewDidDisappear override.
First, I find it easiest to put all your notication definitions in an extension:
extension Notification.Name {
static let modalHasDisappeared = Notification.Name("ModalHasDisappeared")
}
This helps not only reduce string typos but also is allows Xcode's code completion to kick in.
Next, in your first view controller, ad an observer to this notification:
init() {
super.init(nibName: nil, bundle: nil)
NotificationCenter.default.addObserver(self, selector: #selector(modalHasDisappeared), name: .modalHasDisappeared, object: nil)
}
required init?(coder: NSCoder) {
super.init(coder: coder)
NotificationCenter.default.addObserver(self, selector: #selector(modalHasDisappeared), name: .modalHasDisappeared, object: nil)
}
#objc func modalHasDisappeared() {
print("modal has disappeared")
}
I've added both forms of init for clarity. Since you are using a storyboard, I'd expect that init(coder:) is the one you need.
Finally, just send the notification when the modal has disappeared:
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
NotificationCenter.default.post(name: .modalHasDisappeared, object: nil, userInfo: nil)
}
This sends no data, just the fact that the modal has disappeared. If you want to send data - say, a string or a table cell value, change the object parameter to it:
NotificationCenter.default.post(name: .modalHasDisappeared, object: myLabel, userInfo: nil)
And make the following changes in your first VC:
NotificationCenter.default.addObserver(self, selector: #selector(modalHasDisappeared(_:)), name: .modalHasDisappeared, object: nil)
#objc func modalHasDisappeared(_ notification:Notification) {
let label = notification.object as! UILabel!
print(label.text)
}
Last notes:
To repeat, note that by declaring an extension to Notification.Name, I've only have one place where I'm declaring a string.
There is no code in AppDelegate or SceneDelegate, nor any references to `UIApplication(). Try to think of the view (and view controller) as appearing/disappearing, not background/foreground.
While the first view is visually in the background, it's still visible. So the trick is to code against the modal view disappearing instead.

Swift - Update multiple viewControllers

I would like to update several views, at the same time.
In my app, you can change your name. When the user change its name, would like to update 4 ViewControllers, to show the new name.
I use to use delegates to update one ViewController, but given that I would like to update more than 1, I don't think that's possible. (Maybe I'm wrong?)
I've heard about NotificationCenter, but I don't know if it's the best way to achieve that.
Something like that:
NotificationCenter.default.addObserver(self, selector: #selector(self.reload(_:)), name: NSNotification.Name(rawValue: "uploaded"), object: nil)
Is it good to do that?
What do you think? How would you do this?
You have many ways to do that, this depends on how your project struct.
If all views are in the same ViewController you can just listen some delegate and update manually all views, like, self.view1.updateName(newName), self.view2.updateName(newName)
else if you have many ViewControllers you can do something like Singleton and get the name when viewWillAppear, (I prefer don't use Singleton)
Example
class MyInfo{
public static let shared = MyInfo();
private(set) var name:String?
private init(){}
func update(name:String){
self.name = name;
}
}
And on your viewWillAppear you can do something like this
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated);
self.label.text = MyInfo.shared.name;
}
I like so much flux pattern, with ReSwift, but I think it can be very complex for a small problem in your case.
I hope was helpful

How to call function from viewcontroller in appdelegate? (Swift)

So basically I am trying to run the function Refresh (Located in ViewController.swift) in AppDelegate.swift. After searching for the better part of 5 hours I can't figure out why this isnt working.
here is my function:
func Refresh(){
if(Count==0 && QuickActionChecker==true){
Previous.text=String(TipArray[CountCurrent])
Current.text=String(TipArray[CountCurrent])
Deliveries.text="\(Runs)"
}
if(Count>0 && QuickActionChecker==true){
Previous.text=String(TipArray[CountCurrent-1])
Current.text=String(Total)
Deliveries.text="\(Runs)"
}
}
In my Appdelegate.swift I have initialized the ViewController by setting it to Main:
let Main = ViewController()
and here is where I'm attempting to run the function from (force touch quick action from the homescreen):
func application(_ application: UIApplication, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: #escaping (Bool) -> Void) {
if shortcutItem.type == "com.Sicarix.Ticket.Add5"{
if(CountCurrent==0){
TipArray.append(5)
Count=Count+1
CountCurrent=CountCurrent+2
Total=TipArray.reduce(0) {$0+$1}
Runs=Runs+1
QuickActionChecker=true
Main.Refresh()
}
else{
TipArray.append(5)
Count=Count+1
CountCurrent=CountCurrent+1
Total=TipArray.reduce(0) {$0+$1}
Runs=Runs+1
QuickActionChecker=true
Main.Refresh()
}
}
}
however, when I try to use the shortcut it opens the app to a white screen and stays there. Console is spitting out:
fatal error: unexpectedly found nil while unwrapping an Optional value
the code runs fine when I remove the Main.Refresh(), it just doesn't update the labels in my app, hence the need for the function.
Please help, I'm so ready to move on past this bug....
also please bear in mind that I haven't even been coding in swift for a week yet, so please break down what was wrong as best you can. TIA
Change your viewController object
let nextVC = storyboard?.instantiateViewController(withIdentifier:"ViewController") as! ViewController
The problem is that you are instantiating a new ViewController using ViewController() and that ViewController isn't added to your view controller hierarchy.
You need to use your Storyboard to instantiate the ViewController by using let mainVC = UIStoryboard(name: "Main", bundle: "nil").instantiateViewController(withIdentifier:"ViewController") as! ViewController and make sure your identifier is set up in Storyboard.
If you are not using Storyboard, another solution is to store the text you want to display on the UI in data source variables and only update those variables in 'AppDelegate', then load the content of those variables onto your labels in 'viewDidLoad' or 'viewWillAppear'.

Simple data transfer within viewcontroller without transition

So i am new to app development and i am trying to set up a very simple delegation/protocol pattern. I have been searching and trying different tutorials but can't seem to find anything that works and am getting in such a muddle. Please can somebody help. I will break i down so that its really clear as to what i need -
I have two view controllers, 'DetailedVC' and 'SelectionsVC'.
DetailedVC has a variable called -
var sendingData = (choice: "", choiceValue:0.0)
and
UIbutton buttonSelectTapped
SelectionsVC has a variable called -
var recievedData = (choice: "", choiceValue:0.0)
And all i want to do is send the data from the variable 'sendingData' in DetailedVC when the button (buttonSelectTapped) is tapped to the SelectionsVC and store it in the variable 'recievedData'. I do not want the VC to transition from one to the other or anything to be sent back, only to send the data to the other VC.
Then when the user views that controller 'SelectionsVC' at whatever stage, the data will be called in the viewDidLoad when loading that controller.
Use NSUserDefault to pass data between viewcontroller if you do not want the VC to transition from one to the other or anything to be sent back, only to send the data to the other VC.
DetailedVC Code
func viewDidLoad() {
NSUserDefaults.standardUserDefaults().removeObjectForKey("selectedData")
}
func didTapButtonSelectTapped() {
NSUserDefaults.standardUserDefaults().setDouble(sendingData , forKey: "selectedData")
}
SelectionsVC code
func viewDidLoad() {
if(NSUserDefaults.standardUserDefaults().doubleForKey("selectedData")) {
recievedData = NSUserDefaults.standardUserDefaults().doubleForKey("selectedData")
}
}
But as your question title describe their is no use of protocol/delegate in above code.
Passing Data on transition from viewcontroller :
DetailedVC Code
func didTapButtonSelectTapped() {
let vc = SelectionsVC()
vc.recievedData = sendingData
self.presentViewController(vc, animated: true, completion: nil)
}

performSegue after completion handler

ANSWER BELOW
Im facing a little issue that you may help me with.
the app Im working on allows you to request for content based on your location.
the first ViewController is somewhat a form that grab your location / a specified location + some other information to target specific answers.
I need to perform a segue to pass the "question" variables to my second ViewController where I load "answers" with a query based on the question details.
What is causing me trouble is that, whenever the question is geolocalized, I can't retrieve the information using prepareForSegue because it doesn't wait for the geoPoint to be made (completed).The second controller display my latitude and longitude as nil.
I see that I can call the "prepareForSegue" method using "perfomSegueWithIdentifier", and retrieve the information in my second view controller but it perform the segue twice... How can I trigger the segue only when Im ready but using the prepareForSegue data parameter I need to preserve?
Is there a way to pass variable from one controller to another using performSegue?
Any help would be awesome.
Also, while I don't think the code is relevant for my question, here is the code I use.
geoPointing method
#IBAction func doPostQuestion(sender: UIButton) {
var thereQ:PFObject = PFObject(className: "tquestion")
if(somewhereLabel.text == "my location"){
println("Location is geolocalized")
PFGeoPoint.geoPointForCurrentLocationInBackground {
(geoPoint: PFGeoPoint!, error: NSError!) -> Void in
if error == nil {
self.geoLati = geoPoint.latitude as Double
self.geoLong = geoPoint.longitude as Double
self.performSegueWithIdentifier("goto_results", sender:self) // call prepareForSegue when ready but implies to have a segue done on click... (performed twiced)
}
}
}
self.navigationController?.popToRootViewControllerAnimated(true)
}
prepareForSegue
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if(segue.identifier == "goto_results"){
// Get Label
let theDestination = (segue.destinationViewController as displayAnswersViewController)
theDestination.lat = self.geoLati
theDestination.lng = self.geoLong
}
}
ANSWER SOLUTION:
As suggested, to solve this problem you just need to create your segue from your viewController1 to your viewController2 and not from a button. This way you can trigger prepareForSegue programatically using the "performSegue" method that will call prepareForSegue anyway.
To solve this problem you just need to create your segue from your viewController1 to your viewController2 and not from a button. This way you can trigger prepareForSegue programatically using the "performSegue" method that will call prepareForSegue anyway.