In the past I would do:
MainViewController pushes ProfileViewController
In willPerformSegue, give the target ProfileViewController access to a profile instance
Also set target delegate to self
ProfileViewController allows the user to edit their profile
User presses save
ProfileViewController calls sends message didSave to delegate and pops out
MainViewController handles the didSave by saving the model to disk
All of this still works in Swift. My question is: is this still the favored way to handle inter-VC communication in the Swift era?
Yes, it is. Or rather you can do it the same way.
Swift is just another language, which uses the same libraries.
Things you describe related to those libraries, the notion of UIViewController is defined within them, so everything is done the same way.
The delegate pattern works well, but it's not the only way to communicate between controllers, and it can't be "best" in all cases. The best way depends on the task you're trying to solve, not the language you use.
My question is: is this still the favored way to handle inter-VC communication in the Swift era?
With the caveat that the method name is -performSegueWithIdentifier:sender:, that's fine way to do it, and as far as I know it's still fine under Swift. Apple didn't make any announcement at WWDC 2015 that things have changed in this respect, and the UIViewController interface doesn't suggest any more compelling ways to configure a view controller during a segue.
MainViewController handles the didSave by saving the model to disk
That's also fine, since MainViewController is ProfileViewController's delegate, and it sounds like ProfileViewController isn't aware of the entire model. In other circumstances, it might make sense for a view controller to use the model directly, and for the model to handle saving. But that's a design issue, not a Swift vs. Objective-C issue.
Related
Background: In order to make web requests to an API endpoint, I need to scrape a website and retrieve a token every 25-30 seconds. I'm doing this with a WKWebView and injecting some custom JavaScript using WKUserScript to retrieve AJAX response headers containing the token. Please focus on the question specifically and not on this background information - I'm attempting this entirely for my own educational purposes.
Goal
I will have different 'model' classes, or even just other UIViewControllers, that may need to call the shared UIViewController to retrieve this token to make an authenticated request.
Maybe I might abstract this into one "Sdk" class. Regardless, this 'model' SDK class could be instantiated and used by any other ViewController.
More info
I would like to be able to call the UIViewController of the WKWebView and retrieve some data. Unless I re-create it every 25 seconds, I need to run it in the background or share it. I would like to be able to run a UIViewController 'in the background' and receive some information from it once WKWebView has done it's thing.
I know there are multiple ways of communicating with another ViewController including delegation and segueing. However, I'm not sure that these help me keep the view containing the WKWebView existing in the background so I can call it's ViewController and have it re-perform the scrape. Delegation may work for normal code, but what about one that must have the view existing? Would I have to re-create this WKWebView dynamically each time a different model, or view controller, were to try and get this token?
One post suggests utilising ContainerViewControllers. From this, I gather that in the 'master' ViewController (the one containing the other ones), I could place the hidden WKWebView to do it's thing and communicate to the child view controllers that way via delegation.
Another post suggests using AppDelegate and making it a shared service. I'm completely against using a Singleton as it is widely considered an anti-pattern. There must be another way, even if a little more complex, that helps me do what I want without resorting to this 'cheat'.
This post talks about communicating between multiple ViewControllers, but I can't figure out how this would be useful when something needs to stay running and executing things.
How about any other ways to do this? Run something in a background thread with a strong pointer so it doesn't get discarded? I'm using Xcode 9.2, Swift 4, and iOS 11. As I'm very new to iOS programming, any small code examples on this would be appreciated.
Unfortunately, WKWebView must be in the view hierarchy to use it. You must have added it as a sub view of an on-screen view controller.
This was fine for me. I added this off-screen so it was not visible. Hidden attribute might have worked as well. Either way you must call addSubview with it to make it work.
There are some other questions and answers here which verify this.
Here is a way if you don't wish to use a singleton.
1- In the DidFinishlaunchingWithOptions, Make a timer that runs in the background and call a method inside the app delegate Called FetchNewToken.
2- In FetchNewToken, make the call needed and retrieve the new token (you can use alamofire or any 3rd library to make the call easier for you).
Up on successfully retrieving the token, save it in NSUserDefaults under the name upToDateToken
You can access this token anywhere from the application using NSUserDefaults and it will always be up to date.
I have a situation where I need to share WCSession among multiple WKInterfaceControllers. Singleton approach won't work, once you set delegate to a class, all delegates in the other classes are invalidated. Scenario: interface A send and receive data, based on the data content, present interface B. Tap on interface B, will request and receive additional data. How would you share the WCSession between A and B ?
The other answer doesn't explain that an app-wide session would work.
You can use an app-wide WCSession singleton which would be available to all your interface controllers. You simply instantiate a session manager very early in the app life cycle, and let it be its own delegate.
Instead of trying to make each interface controller handle the session delegation (which won't work well), a session manager (singleton) can handle all the transfers for your interface controllers.
As mentioned in the other answer, you could then use notifications to let interested interface controllers know when their new data arrives.
Using a modular approach, such as a session or data manager, helps to keep such code out of a controller, where it really doesn't belong. It also makes it easier to test and utilize each module.
I won't repeat the code here, as there have already been several existing answers posted on Stack Overflow, as well as
articles on the web, which cover this technique in detail. For example:
Using WCSession with more than one ViewController
WatchConnectivity: Say Hello to WCSession
You'll often find these types of answers within narrower questions that ask how to share data between, say, a watch app and its complication controller.
Use NSNotification and listen for changes in all view controllers.
My goal is to send a dictionary to the watchKit from iOS app prior to the watchKit's app launch. I'm using interactive messaging (sendMessage) to quickly transfer the dictionary.
The issue is - dictionary is created inside the MainViewController. If i declare the WCSession and activate it inside the MainViewController i can transfer the data to the watchKit on the simulator without any problem. But when i test the process on a real device - the iOS app never gets called.
Waking the app in the background is done by declaring and activating the WCSession inside the AppDelegate, but there's another blocker - i cannot create the dictionary - because multiple variables for its creation are declared inside the MainViewController.
I tried a third approach - wrapping the WCSession inside a singleton (suggested by Natasha the robot). The only drawback of this framework is that the Interactive messaging never works and wasn't ever tested by Natasha herself.
So i'm confused - what do i do to send the dictionary to the watchKit?
Thanks for any insights
You need to figure out a way to get the dictionary created outside of MainViewController. Perhaps you can write a class method in the controller that creates and returns the dictionary so that it can be used from both AppDelegate and MainViewController.
You should use a data store to hold your dictionary, then have it create its data based on the variables passed to it by the main view controller.
Once that occurs, you can use the WCSession manager to transfer the data store's dictionary.
I know Natasha covers these aspects in her tutorial. If you have a specific question as to how to do that, you'd really need to post code showing what you tried, along with a description of what's not working.
If the watch asks for data, but it has not been created yet, you need to return a "No data yet" reply so the watch can display a message telling the user to open the app and set the view controller's variables used for creating the data.
It really is better to separate and encapsulate responsibilities into these different components. The view controller shouldn't need to contain any code related to creating or transferring the dictionary.
Having said all that...
I cannot create the dictionary - because multiple variables for its creation are declared inside the MainViewController
This really sounds like an XY problem. You've been focused on the problem of "sending" this dictionary of large arrays that you have to create, when there's likely an easier way to accomplish what you're actually trying to do with this large dictionary in the first place.
For one, I'd wonder why you're sending that huge computed data set to the watch for it to do something with, instead of also handling that computation on the phone side, then sending a very small set of "results".
Perhaps you should describe the real Y problem you want to solve on the watch, instead of asking us for an X solution which may end up being unnecessary.
Ok so i have been trying to figure out how to do this for a while but i did not seem to find way to do it. Also i would like the proper way to do this.
A server that i have is sending notifications every 30 seconds to my device. Lets say i am in ViewController B, but the data that is received by the notification is ment to be displayed/used in ViewController A.
Lets say i received two notifications while i was in ViewController B. Then i navigate to ViewController A. How would i get it to display the most recent data that was received by the notification?
You should receive the notification in a (global) 3rd object that will store them, then when the VC A is displayed you'll easily retrive them from that object...
Follow the "shared instance" path used by many iOS classes (even if someone don't like it 'cause they read singletons are evil, I think this's the perfect case to use it).
You can solve it this way:
Create at startup your singleton class that will receive the notifications and keep them in a queue.
Add to the singleton methods to read/consume the notification queue.
From any class you need the data (i.e. your view controller) get the infos you need via the methods above.
This solution keep data manager (notification handling) and presentation (the view controller) separated, I don't see any real cons...
Again, I know singletons have a bad reputation (and often people abuse of this pattern) but you know Apple's NSNotificationCenter have a +defaultCenter class method that return the shared instance (another word for singleton) so I'm quite sure this's the case to use it.
here http://www.daveoncode.com/2011/12/19/fundamental-ios-design-patterns-sharedinstance-singleton-objective-c/ you can find a good example how to implement the +sharedInstance (or +defaultCenter or whatever you want to call it) method.
Hope this help.
I have an app that fetches data from a server using NSURLSessionDataTask. As of right now I am starting my HTTP GET Request in the init method of the UIViewController that displays the data. Is this the best/smartest place to kick off an HTTP request? If not, where should I do it?
I'm asking this question because when I exit my app and it goes into the background (and is not killed) and then re-open my application, the HTTP request is not fired off (because it is in the UIViewControllers init method) and the data being displayed is not up to date with what's on the server. I've tried putting it in viewDidLoad but this method is not called upon entering the foreground, neither is viewWillAppear nor viewDidAppear.
Should I be doing all of my HTTP requests in one of the UIApplications life cycle methods in my appDelegate?
In short, where is the best place to make HTTP requests in iOS?
Thank you, I can post code or explain more if needed.
first off, this is a huge question and probably impossible to answer fully here, but hopefully I can point you in the right direction so you can learn how to fish. :)
To stick to the Model-View-Controller paradigm, you will want to create a separate object for making your HTTP requests. An HTTP request would be considered part of your model. The benefit of this is being able to use your model in other iOS apps you create, for example.
As for where to put all of this stuff and what's the best design.... One thing that strikes me in your question is you want the data being displayed in your app to be up to date with what is on the server. On a high level, a really good way to do this is to use iOS's ability to multitask and perform functions for you when you app is in the background. You would need to register with the OS as an app that performs fetches to a server in the background.
According to Apple's documentation, "In Xcode 5 and later, you declare the background modes your app supports from the Capabilities tab of your project settings. Enabling the Background Modes option adds the UIBackgroundModes key to your app’s Info.plist file." From there you would need to research the UIApplicationDelegate's protocol methods – application:performFetchWithCompletionHandler: and -application:handleEventsForBackgroundURLSession:completionHandler:.
Also, you will need to look into NSURLSession a little more. If you want to use background fetching, NSURLSessionDataTask is not supported. You will need to use NSURLSessionDownloadTask, save the response to a file and process it however you need to. Also, as the app delegate method name above implies, you will need to read the NSURLSessionConfiguration Class Reference, specifically about backgroundSessionConfiguration.
The really cool thing about all of this is, after you have implemented it, your app UI will be up to date for the user – even if your app was killed by the user or by the OS. Pretty nifty.
Good luck and hope this all helps. I hope I didn't miss any other big pieces here.
I think it a personal preference. So I personally do it on the model objects. Lets say I have a Car object and a ShowroomViewController. I always declare a class method to Car object to call service to get all the cars.
#interaface Car
+(void)fetchCarsWithCompletionHandler:(void (^)(NSArray* cars, NSError *error ))handler;
-(void)getDetailsWithCompletionHandler:(void (^)(Car* car, NSError *error ))handler;
#end
Then call the class method on viewWillAppear(If I need to update the cars very often) or viewDidload(If I need to call the service once).
The other trick I mostly do is define a flag in the view controller like
#interface ShowroomViewController
#property(assign)BOOL needsModelUpdate;
#end
and I update the modal conditionally.
#implementation ShowroomViewController
-(void)viewWillAppear:(BOOL)animated{
if(self.needsModelUpdate){
[self fetchModel]
}
}
-(void)fetchModel{
__block __weak ShowroomViewController *weakRef=self;
[Car fetchCarsWithCompletionHandler:^(NSArray *cars, NSError *error) {
[weakRef setCars:cars];
[weakRef.tableView reloadData];
}];
}
#end
The reason I define this flag is I can change it somewhere else lets say applicationDidEnterBackground: method the change the flag. Or you can use KVO but I always find it overkill .
Do it however you want.
Personally, I create a class specifically for all communication with the server. Actually, my App has around 20 classes for different parts of the communication process. But yours is probably less complicated.
Add an instance of the class as an object in an xib file or else create an instance of it inside the app delegate's init method.
Use didEnterForeground to tell the other class that it needs to do it's stuff, but still use the init method to create an instance of the class.