I'm attempting to deep link from a Push Notification into a part of my app. I have a messages section in my app, and I want to link right into the message VC that correlates to the Push that was sent.
This is what I have done so far, but I'm not sure if I am even on the right path. The APN comes from Parse, in JSON in what I believe is a NSDictionary.
func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject]) {
if let aps = userInfo["aps"] as? NSDictionary {
if let vc = aps["vc"] as? NSDictionary {
if vc == "messages" {
if let message = aps["link"] {
}
}
}
}
}
Am I on the right path here? Any Suggestions?
Thanks!
You are able to do it the way you proposed. Like anything you are able to do it multiple ways.
Your current path means you will extract and parse the json data. From there spin up the correct view from your app delegate (or segue from the initial view).
If you decide you need an architecture where your app has multiple entry points, there are frameworks that will help you do it.
Bolts Frameworks:
https://github.com/BoltsFramework/Bolts-ObjC
Programmable web had a good article on deep linking:
http://www.programmableweb.com/news/how-to-implement-deep-linking-ios/how-to/2015/07/14
The above links provide a way to structure your app so that custom urls are able to open the app into any part. If there is only going to be one extra entry point, there is probably not much point re architecting your app.
Related
What I am trying to achieve
Handle custom url type when app is launched or not launched.
What I did
Define a custom url type
Implement the .onOpenURL in my View like this
(only to debug, I observe this custom event so that I can check when it occurs)
.onOpenURL(perform: { url in
if (url.host! == "web" && url.pathComponents.count > 1) {
NotificationCenter.default.post(name: .receivedIncomingURL, object: nil)
}
})
The problem and what I tried
The app does not react on the custom url when it is not launched. According to the docs [1], I had to implement a method in the AppDelegate which I did like this (event tried to kill the app with fatalError() but the code block is not executed.
func application(_ application: UIApplication,
open url: URL,
options: [UIApplicationOpenURLOptionsKey : Any] = [:] ) -> Bool {
NotificationCenter.default.post(name: .receivedIncomingURL, object: nil)
// event tried fatalError() here.
}
Questions
The documentation also says, that whenever you opted to Scene (UIKit Scene), you have to use the SceneDelegate which my App does not have. My SwiftUI #main App contains the definition var body: some Scene { // } - does that mean, that I opted to Scene in the way the documentation means it? So I have to implement a SceneDelegate in the SwiftUI App?!? Or is there another way to react on the custom url in a closed app?
[1] https://developer.apple.com/documentation/xcode/defining-a-custom-url-scheme-for-your-app
So I finally came up with a solution. It is not needed to implement a SceneDelegate, the .onOpenUrl modifier works like a charm but:
It can be used on every view and on the App itself.
Since I used it on my main view everything worked when the app was loaded
What I forgot to mention is, that I have a custom "splash view" with a video animation. This is loaded only once when the app starts and since neither the app itself nor the splashscreen implemented .onOpenUrl, my app does not recognize incoming urls when it is started for the first time
I now use the same snippet not on the main view but on the app and everything works great.
Sometime thinking about the problem 10289390123 times helps..
I want my app language to change on button click..
My code is:
#IBAction func convertlang(_ sender: Any) {
if L102Language.currentAppleLanguage() == "en" {
L102Language.setAppleLAnguageTo(lang: "ar")
UIView.appearance().semanticContentAttribute = .forceRightToLeft
} else {
L102Language.setAppleLAnguageTo(lang: "en")
UIView.appearance().semanticContentAttribute = .forceLeftToRight
}
}
let APPLE_LANGUAGE_KEY = "AppleLanguages"
class L102Language {
class func currentAppleLanguage() -> String{
let userdef = UserDefaults.standard
let langArray = userdef.object(forKey: APPLE_LANGUAGE_KEY) as! NSArray
let current = langArray.firstObject as! String
return current
}
class func setAppleLAnguageTo(lang: String) {
let userdef = UserDefaults.standard
userdef.set([lang,currentAppleLanguage()], forKey: APPLE_LANGUAGE_KEY)
userdef.synchronize()
}}
and this works fine and convert the language when i click on the button..
the problem is i need to restart the app to see the language change..
i searched for this but most of them were for objective-c and tried some for swift but didn't work..
how to do this?
You can't change the language at runtime with normal button click. If you really need that, you need to use a custom localization system instead.
To change the app language during runtime, I have done this manually by creating two different storyboards. I saved the language preference in NSUserDefaults but NOT with the key AppleLanguages and then called AppDelegate's didFinishLaunchingWithOptions method to select storyboard.
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"MainEnglish" bundle:nil];
UINavigationController *navigationcontroller=[[UINavigationController alloc]init];
RegistorViewController *registor=[storyboard instantiateViewControllerWithIdentifier:#"Registor"];
[self.window setRootViewController:navigationcontroller];
[self.window makeKeyAndVisible];
[navigationvontroller pushViewController:registor animated:NO];
Though this is not what apple wants a developer to do in runtime.
Someone even asked an Apple engineer and this is the response:
In general, you should not change the iOS system language (via use of the AppleLanguages pref key) from within your application. This goes against the basic iOS user model for switching languages in the Settings app, and also uses a preference key that is not documented, meaning that at some point in the future, the key name could change, which would break your application.
If you want to switch languages in your application, you can do so via manually loading resource files in your bundle. You can use NSBundle:pathForResource:ofType:inDirectory:forLocalization: for this purpose, but keep in mind that your application would be responsible for all loading of localized data.
I had faced the same problem in my project recently. You are using the correct code to change app language but it change only string file data not the storyboard. For complete localisation you have to restart the app again, because there is no any another way to do this. You can show a popup to user "Please restart your app to change language completely". Because we can kill the app programatically, but after doing this there is no way to restart the app again.
This might not be the most iOS-typical way of doing things, but I tend to treat static content like any other data in the app, especially when I'm using VIPER (although other patterns would work) and a unidirectional data flow.
With that in mind, for this I would have a service accessing a store of static content keyed of the user's language preference. When that changes I would trigger a refresh of the content.
This has worked well for projects when I also have server data as well as local data that needs to be localised.
To be fair, I've never used the standard tools much. And I suspect this may not be ideal for right-to-left language types.
I am fairly new in Google Analytics in general, so please be patient with me and my questions.
If I am using the Google Analytics for web, by putting in the tracking code in my web header, GA will automatically collect data (visitors, page view, sources, etc)
For mobile app, I need to put the plist (iOS) or json (Android) into the build and compile.
My understanding is that it's not enough just by putting the plist or json file into the app, right?
I would need to implement each and every single thing I want to track. For example, if i want a pageview (screens), then I would need to implement it inside my app code
https://developers.google.com/analytics/devguides/collection/ios/v3/screens
so it's not automatic like in web, where I put the script on header and it works right away.
Is that correct?
I haven't used GA for web, so I can't compare it to GA for mobile.
But I believe you are correct that you need to implement everything you want to track.
It's not too difficult. Using GA 3.11 for iOS, in Swift 3.0, the first thing I do is set up the shared GA instance in my app delegate's didFinishLaunchingWithOptions method:
GAI.sharedInstance().trackUncaughtExceptions = true
GAI.sharedInstance().dispatchInterval = 120
GAI.sharedInstance().logger.logLevel = GAILogLevel.info
GAI.sharedInstance().tracker(withTrackingId: "YOUR GA ID GOES HERE")
To track a screen view, I do this in viewWillAppear of each view controller:
if let tracker = GAI.sharedInstance().defaultTracker {
tracker.set(kGAIScreenName, value: "YOUR SCREEN NAME GOES HERE")
tracker.send(GAIDictionaryBuilder.createScreenView().build() as [NSObject : AnyObject])
}
To send an event:
let tracker = GAI.sharedInstance().defaultTracker
tracker?.send(GAIDictionaryBuilder.createEvent(withCategory: "YOUR CATEGORY", action: "YOUR ACTION", label: "YOUR LABEL", value: NSNumber(integerLiteral: YOURINTEGERVALUE)).build() as NSDictionary as [NSObject : AnyObject])
Setting custom dimensions and sending screen views with dimensions is similar. That's about as far as I've gone with GA.
My iOS 8.0 + app is essentially a dictionary app, presenting a read-only data set to the user in an indexed, easily navigable format. I have explored several strategies for loading the static data, and I have decided to ship the app with several JSON data files that are serialized and loaded into a Core Data store once when the app is first opened. The call to managedObjectContext.save(), therefore, will happen only once in the lifetime of the app, on first use.
From reading Apple's Core Data Programming Guide in the Mac Developer Library (updated Sept. 2015), I understand that Apple's recommended practice is to 1) separate the Core Data stack from the AppDelegate into a dedicated DataController object (which makes it seem odd that even in Xcode 7.2 the Core Data stack is still put in the AppDelegate by default, but anyway...); and
2) open (and, I assume, seed/load) the persistent store in a background thread with a dispatch_async block, like so :
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0)) {
//(get URL to persistent store here)
do {
try psc.addPersistentStoreWithType(NSSQLiteStoreType, configuration: nil, URL: storeURL, options: nil)
//presumably load the store from serialized JSON files here?
} catch { fatalError("Error migrating store: \(error)") }
}
I'm just getting started learning about concurrency and GCD, so my questions are basic:
1) If the data set is being loaded in a background thread, which could take some non-trivial time to complete, how does the initial view controller know when the data is finished loading so that it can fetch data from the ManagedObjectContext to display in a UITableView ?
2) Along similar lines, if I would like to test the completely loaded data set by running some fetches and printing debug text to the console, how will I know when the background process is finished and it's safe to start querying?
Thanks!
p.s. I am developing in swift, so any swift-specific tips would be tremendous.
Instead of trying to make your app import the read-only data on first launch (forcing the user to wait while the data is imported), you can import the data yourself, then add the read-only .sqlite file and data model to your app target, to be copied to the app bundle.
For the import, specify that the persistent store should use the rollback journaling option, since write-ahead logging is not recommended for read-only stores:
let importStoreOptions: [NSObject: AnyObject] = [
NSSQLitePragmasOption: ["journal_mode": "DELETE"],]
In the actual app, also specify that the bundled persistent store should use the read-only option:
let readOnlyStoreOptions: [NSObject: AnyObject] = [
NSReadOnlyPersistentStoreOption: true,
NSSQLitePragmasOption: ["journal_mode": "DELETE"],]
Since the bundled persistent store is read-only, it can be accessed directly from the app bundle, and would not even need to be copied from the bundle to a user directory.
Leaving aside whether loading a JSON at the first startup is the best option and that this question is four years old, the solution to your two questions is probably using notifications. They work from all threads and every listening class instance will be notified. Plus, you only need to add two lines:
The listener (your view controller or test class for question 2) needs to listen for notifications of a specific notification name:
NotificationCenter.default.addObserver(self, selector: #selector(ViewController.handleMySeedNotification(_:)), name: "com.yourwebsite.MyCustomSeedNotificationName", object: nil)
where #objc func handleMySeedNotification(_ notification: Notification) is the function where you are going to implement whatever should happen when a notification is received.
The caller (your database logic) the sends the notification on successful data import. This looks like this:
NotificationCenter.default.post(name: "com.yourwebsite.MyCustomSeedNotificationName", object: nil)
This is enough. I personally like to use an extension to Notification.Name in order to access the names faster and to prevent typos. This is optional, but works like this:
extension Notification.Name {
static let MyCustomName1 = Notification.Name("com.yourwebsite.MyCustomSeedNotificationName1")
static let MyCustomName2 = Notification.Name("CustomNotificationName2")
}
Using them now becomes as easy as this: NotificationCenter.default.post(name: .MyCustomSeedNotificationName1, object: nil) and even has code-completion after typing the dot!
So I have an app written in swift that I would like to use deep links for. A user would click on one that in the route of the URL had different pieces of information such as the id of the post or the number of likes it has or something like that. I understand that you put this in the AppDelegate:
func application(application: UIApplication,openURL url: NSURL,sourceApplication sourceApplication: String?,annotation annotation: AnyObject?) -> Bool {
println(url.host as String!)
return true
}
And that prints everything after appname://. But how can I get url.host to my view controller to be parsed into the information I need. If the declaration of URL was outside of that function then I could use this:
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let InfoFromDeepLink = appDelegate.url.host
but since it's inside of that bulky Objective-C function, I'm helpless. How do you do this? I'm completely baffled. I can't even set the function to return url.host because it doesn't allow that. That's what I would do in Javascript. If you also know Javascript maybe put it in terms that a web developer would understand because I'm pretty new to this. This has to be so simple for a Swift developer right? I feel so stupid.
I would recommend using this cocoapods package called DeepLinkKit HERE IS THE SOURCE CODE
pod "DeepLinkKit"
You can simple route and have an error handling:
// Matches the URL.
router[#"timeline"] = ^{ … }
// Does not match the URL.
router[#"/timeline"] = ^{ … }
Here is a good tutorial that shows you in depth how it works.
Apple no longer supports Deep Links. It is now called Universal Links and works a bit differently.
Source
Now that Apple no longer supports URI schemes for deep linking, developers must implement Universal Links in order to deep link properly on iOS. If you are already using URI schemes, check out our blog on transitioning to Universal Links.
From: HERE
And HERE is another article on Universal Links and what they are.