Detect first launch of iOS app [duplicate] - ios

This question already has answers here:
How to detect Apps first launch in iOS?
(9 answers)
Closed 5 years ago.
I am trying to find a way in Swift to detect the first launch.

Typically you would write a value to NSUserDefaults to indicate that an app has launched before.
let launchedBefore = NSUserDefaults.standardUserDefaults().boolForKey("launchedBefore")
if launchedBefore {
print("Not first launch.")
}
else {
print("First launch, setting NSUserDefault.")
NSUserDefaults.standardUserDefaults().setBool(true, forKey: "launchedBefore")
}
UPDATE - Swift 3
let launchedBefore = UserDefaults.standard.bool(forKey: "launchedBefore")
if launchedBefore {
print("Not first launch.")
} else {
print("First launch, setting UserDefault.")
UserDefaults.standard.set(true, forKey: "launchedBefore")
}

I kinda always need this so I put it in a category
General Usage:
let isFirstLaunch = UserDefaults.isFirstLaunch()
Usage inside your AppDelegate
// use this if you need to refer to it later
var optionallyStoreTheFirstLaunchFlag = false
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
optionallyStoreTheFirstLaunchFlag = UserDefaults.isFirstLaunch()
// .. do whatever else
return true
}
Some important considerations:
This flag is only set on the first invocation. If you want to know
about the first launch multiple times throughout different screens,
set a variable you can later refer to, as per the
'optionallyStoreTheFirstLaunchFlag' example.
In iOS, apps are usually never shut down. Apps are backgrounded, foregrounded,
state-saved to flash memory, but they are only relaunched if they're
force shutdown by the user (rare) or the user restarts their phone.
So if you store it in a variable, it could potentially stick around
for a long time. Manually reset it once you're done with showing all
the tutorial screens and whatnot.
Swift 4
Put the following in UserDefaults+isFirstLaunch.swift
extension UserDefaults {
// check for is first launch - only true on first invocation after app install, false on all further invocations
// Note: Store this value in AppDelegate if you have multiple places where you are checking for this flag
static func isFirstLaunch() -> Bool {
let hasBeenLaunchedBeforeFlag = "hasBeenLaunchedBeforeFlag"
let isFirstLaunch = !UserDefaults.standard.bool(forKey: hasBeenLaunchedBeforeFlag)
if (isFirstLaunch) {
UserDefaults.standard.set(true, forKey: hasBeenLaunchedBeforeFlag)
UserDefaults.standard.synchronize()
}
return isFirstLaunch
}
}

Swift 3
extension UserDefaults {
var hasLaunchBefore: Bool {
get {
return self.bool(forKey: #function)
}
set {
self.set(newValue, forKey: #function)
}
}
}
Swift 5 (Property wrappers)
UserDefaultWrapper:
#propertyWrapper
struct UserDefaultWrapper<T> {
let key: String
let defaultValue: T
init(_ key: String, defaultValue: T) {
self.key = key
self.defaultValue = defaultValue
}
var wrappedValue: T {
get {
return UserDefaults.standard.object(forKey: key) as? T ?? defaultValue
}
set {
UserDefaults.standard.set(newValue, forKey: key)
}
}
}
UserDefaultsStore:
struct UserDefaultsStore {
#UserDefaultWrapper("has_launch_before", defaultValue: false)
static var hasLaunchBefore: Bool
}
Usage:
UserDefaultsStore.hasLaunchBefore = false

I refined a bit user n13 answer in order to
have the method always return true during the whole first launch
be an extension to UIApplication
Just use it wherever you want as UIApplication.isFirstLaunch() and be sure to reach it at least once during first execution.
Swift 3
import UIKit
private var firstLaunch : Bool = false
extension UIApplication {
static func isFirstLaunch() -> Bool {
let firstLaunchFlag = "isFirstLaunchFlag"
let isFirstLaunch = UserDefaults.standard.string(forKey: firstLaunchFlag) == nil
if (isFirstLaunch) {
firstLaunch = isFirstLaunch
UserDefaults.standard.set("false", forKey: firstLaunchFlag)
UserDefaults.standard.synchronize()
}
return firstLaunch || isFirstLaunch
}
}
Swift 2
import UIKit
private var firstLaunch : Bool = false
extension UIApplication {
static func isFirstLaunch() -> Bool {
let firstLaunchFlag = "isFirstLaunchFlag"
let isFirstLaunch = NSUserDefaults.standardUserDefaults().stringForKey(firstLaunchFlag) == nil
if (isFirstLaunch) {
firstLaunch = isFirstLaunch
NSUserDefaults.standardUserDefaults().setObject("false", forKey: firstLaunchFlag)
NSUserDefaults.standardUserDefaults().synchronize()
}
return firstLaunch || isFirstLaunch
}
}

Use NSUserDefaults. Register a BOOL key with a value of false. Read the key at launch time; if it's false, set it to true and show the welcome. Next launch, it will be true, you won't show the welcome, problem solved.

In case of Swift In applicationdidFinishLaunchingWithOptions in AppDelegate Add:
if UserDefaults.standard.bool(forKey: "isFirstLaunch") {
UserDefaults.standard.set(true, forKey: "isFirstLaunch")
UserDefaults.standard.synchronize()
}
And Use this wherever you want to.
let isFirstLaunch = UserDefaults.standard.value(forKey: "isFirstLaunch") as? Bool
if isFirstLaunch {
//It's the initial launch of application.
}
else {
// not initial launch
}

I did an edit of n13's post. This code seems cleaner to me. You can call as a class or instance function.
Also, according to apple docs you shouldn't call synchronize() since it's called periodically, unless the app is about to close. I have it called in the AppDelegate in applicationDidEnterBackground(). https://developer.apple.com/library/ios/documentation/Cocoa/Reference/Foundation/Classes/NSUserDefaults_Class/#//apple_ref/occ/instm/NSUserDefaults/synchronize
if NSUserDefaults().isFirstLaunchForUser("me") {
print("First launch")
} else {
print("Not first launch")
}
if NSUserDefaults.isFirstLaunch() {
print("First launch")
} else {
print("Not first launch")
}
extension NSUserDefaults {
static func isFirstLaunch() -> Bool {
let firstLaunchFlag = "FirstLaunchFlag"
if !standardUserDefaults().boolForKey(firstLaunchFlag) {
standardUserDefaults().setBool(true, forKey: firstLaunchFlag)
// standardUserDefaults().synchronize()
return true
}
return false
}
// For multi user login
func isFirstLaunchForUser(user: String) -> Bool {
if !boolForKey(user) {
setBool(true, forKey: user)
// synchronize()
return true
}
return false
}
}

you can use UserDefaults to store the times that App has opened
First:
AppDelegate.swift
let userDefaults = UserDefaults.standard
var currentTimesOfOpenApp:Int = 0
func saveTimesOfOpenApp() -> Void {
userDefaults.set(currentTimesOfOpenApp, forKey: "timesOfOpenApp")
}
func getCurrentTimesOfOpenApp() -> Int {
return userDefaults.integer(forKey: "timesOfOpenApp") + 1
}
each time the App is open, you should add the property currentTimesOfOpenApp, so modify this property in the function func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
self.currentTimesOfOpenApp = getCurrentTimesOfOpenApp()
return true
}
in addition, when the app is closed, you should save the currentTimesOfOpenApp, that is important!
func applicationWillTerminate(_ application: UIApplication) {
saveTimesOfOpenApp()
self.saveContext()
}
Second:
if you want to show the times, you can get this value form UserDefaults to display it on the Label.
ViewController.swift
let delegate = UIApplication.shared.delegate as! AppDelegate
let times = delegate.currentTimesOfOpenApp
timesToOpenAppLabel.text = "\(times)"
the App is open every time, the currentTimesOfOpenApp will be increase. if you delete the App, this value will be reset as 1.

let applicationLaunchedOnce: Bool = {
let launchedOnce = NSUserDefaults.standardUserDefaults().boolForKey(UserDefaultsService.ApplicationLaunchedOnce)
if launchedOnce {
return launchedOnce
} else {
NSUserDefaults.standardUserDefaults().setBool(true, forKey: UserDefaultsService.ApplicationLaunchedOnce)
NSUserDefaults.standardUserDefaults().synchronize()
return false
}
}()

Related

How to handle the redirect URLs for the PianoID SDK iOS?

When I try to connect to Facebook in my iOS application, the app redirects me to the Facebook app and then there is a loop between the two apps, meaning that the user is being sent back and forth between the two apps without being able to complete the login process. I suspect that there is something missing in my code to properly handle the redirect URLs for the PianoID SDK and I would like to know what needs to be done to fix this issue.
this is my appDelegate class :
#objc class AppDelegate: FlutterAppDelegate, PianoIDDelegate {
private var flutterResult: FlutterResult? = nil
#Published private(set) var initialized = false
#Published private(set) var token: PianoIDToken?
var cookiessseion = ""
var startdate = ""
var enddate = ""
var pianoToken=""
func pianoID(_ pianoID: PianoOAuth.PianoID, didSignInForToken token: PianoOAuth.PianoIDToken!, withError error: Error!) {}
func pianoID(_ pianoID: PianoOAuth.PianoID, didSignOutWithError error: Error!) {}
func pianoIDSignInDidCancel(_ pianoID: PianoOAuth.PianoID) {}
override func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
PianoID.shared.endpointUrl = Settings.endpoint.api
PianoID.shared.aid = Settings.aid
PianoID.shared.isSandbox = true
PianoID.shared.signUpEnabled = true
PianoID.shared.googleClientId = Settings.clientId
PianoID.shared.delegate = self
PianoOAuth.PianoIDApplicationDelegate.shared.application(application, didFinishLaunchingWithOptions: launchOptions)
GeneratedPluginRegistrant.register(with: self)
let name = Bundle.main.infoDictionary?["CFBundleName"]
let version = Bundle.main.infoDictionary?["CFBundleShortVersionString"]
GEMConfig.sharedInstance().setAppInfo(name as? String, version: version as? String)
//FirebaseApp.configure()
guard let controller = window?.rootViewController as? FlutterViewController else {
fatalError("rootViewController is not type FlutterViewController")
}
let Channel = FlutterMethodChannel(name: "flutter.native/helper", binaryMessenger: controller.binaryMessenger)
Channel.setMethodCallHandler({
[weak self] (call: FlutterMethodCall, result: #escaping FlutterResult) -> Void in
if(call.method == "testpiano"){
self?.initPianoID()
self?.flutterResult = result
} else {
result(FlutterMethodNotImplemented)
}
})
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
/// Sign In callback
func signIn(result: PianoIDSignInResult!, withError error: Error!) {
if let r = result {
token = r.token
do {
let PianoIDTokenString = try JSONParserSwift.getJSON(object: token)
self.flutterResult?(PianoIDTokenString)
} catch {
print("Unexpected data error: \(error)")
}
} else {
}
}
/// Sign Out callback
func signOut(withError error: Error!) {
token = nil
}
/// Cancel callback
func cancel() {
}
private func initPianoID() {
PianoID.shared.signIn()
}
func openSettings(alert: UIAlertAction!) {
if let url = URL.init(string: UIApplication.openSettingsURLString) {
UIApplication.shared.open(url, options: [:], completionHandler: nil)
}
}
override public func applicationDidBecomeActive(_ application: UIApplication) {
debugPrint("applicationDidBecomeActive")
if #available(iOS 14, *) {
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
ATTrackingManager.requestTrackingAuthorization { status in
}
}
}
}
}
extension Date {
func currentTimeMillis() -> Int64 {
return Int64(self.timeIntervalSince1970)
}
}
And this is the documentation : link

How to add an Avatar image in GetStream iOS Activity Feed component?

My config: XCode 10.3, Swift 5, MacOS Catalina v10.15
I followed the native iOS Activity Feed demo (https://getstream.io/ios-activity-feed/tutorial/?language=python) to successfully add an activity feed to my XCode project.
How do I add an avatar image for each user? Here is what I have tried so far:
I uploaded an avatar image to my backend storage, obtained the corresponding URL, and used a json object to create a new user using my backend server like so:
{
"id" : "cqtGMiITVSOLE589PJaRt",
"data" : {
"name" : "User4",
"avatarURL" : "https:\/\/firebasestorage.googleapis.com\/v0\/b\/champXXXXX.appspot.com\/o\/profileImage%2FcqtGMiITVSOLXXXXXXXX"
}
}
Verified that user was created successfully, but the FlatFeedPresenter view controller shows up with a blank avatar image even though activities in the feed show up correctly. How can I use the user's data.avatarURL property to populate the avatar image correctly?
Here is the StreamActivity ViewController class behind the Main storyboard.
import UIKit
import GetStream
import GetStreamActivityFeed
class StreamActivityViewController: FlatFeedViewController<GetStreamActivityFeed.Activity> {
let textToolBar = TextToolBar.make()
override func viewDidLoad() {
if let feedId = FeedId(feedSlug: "timeline") {
let timelineFlatFeed = Client.shared.flatFeed(feedId)
presenter = FlatFeedPresenter<GetStreamActivityFeed.Activity>(flatFeed: timelineFlatFeed, reactionTypes: [.likes, .comments])
}
super.viewDidLoad()
setupTextToolBar()
subscribeForUpdates()
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let detailViewController = DetailViewController<GetStreamActivityFeed.Activity>()
detailViewController.activityPresenter = activityPresenter(in: indexPath.section)
detailViewController.sections = [.activity, .comments]
present(UINavigationController(rootViewController: detailViewController), animated: true)
}
func setupTextToolBar() {
textToolBar.addToSuperview(view, placeholderText: "Share something...")
// Enable image picker
textToolBar.enableImagePicking(with: self)
// Enable URL unfurling
textToolBar.linksDetectorEnabled = true
textToolBar.sendButton.addTarget(self,
action: #selector(save(_:)),
for: .touchUpInside)
textToolBar.updatePlaceholder()
}
#objc func save(_ sender: UIButton) {
// Hide the keyboard.
view.endEditing(true)
if textToolBar.isValidContent, let presenter = presenter {
// print("Message validated!")
textToolBar.addActivity(to: presenter.flatFeed) { result in
// print("From textToolBar: \(result)")
}
}
}
}
UPDATE:
I updated the AppDelegate as suggested in the answer below, but avatar image still does not update even though rest of the feed does load properly. Set a breakpoint at the following line and found that avatarURL property of createdUser is nil even though streamUser.avatarURL is set correctly.
print("createdUser: \(createdUser)")
Updated AppDelegate code (had to comment out
initialViewController?.reloadData() to address a "Value of type 'UIViewController' has no member 'reloadData'" error -- not sure whether is contributing to the avatar issue.)
import UIKit
import Firebase
import GetStream
import GetStreamActivityFeed
import GoogleSignIn
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
FirebaseApp.configure()
GIDSignIn.sharedInstance()?.clientID = FirebaseApp.app()?.options.clientID
Database.database().isPersistenceEnabled = true
configureInitialRootViewController(for: window)
return true
}
}
extension AppDelegate {
func configureInitialRootViewController(for window: UIWindow?) {
let defaults = UserDefaults.standard
let initialViewController: UIViewController
if let _ = Auth.auth().currentUser, let userData = defaults.object(forKey: Constants.UserDefaults.currentUser) as? Data, let user = try? JSONDecoder().decode(AppUser.self, from: userData) {
initialViewController = UIStoryboard.initialViewController(for: .main)
AppUser.setCurrent(user)
Client.config = .init(apiKey: Constants.Stream.apiKey, appId: Constants.Stream.appId, token: AppUser.current.userToken)
let streamUser = GetStreamActivityFeed.User(name: user.name, id: user.id)
let avatarURL = URL(string: user.profileImageURL)
streamUser.avatarURL = avatarURL
Client.shared.create(user: streamUser) { [weak initialViewController] result in
if let createdUser = try? result.get() {
print("createdUser: \(createdUser)")
// Refresh from here your view controller.
// Reload data in your timeline feed:
// initialViewController?.reloadData()
}
}
} else {
initialViewController = UIStoryboard.initialViewController(for: .login)
}
window?.rootViewController = initialViewController
window?.makeKeyAndVisible()
}
}
The recommended approach is to ensure the user exists on Stream's side in AppDelegate.
extension AppDelegate {
func configureInitialRootViewController(for window: UIWindow?) {
let defaults = UserDefaults.standard
let initialViewController: UIViewController
if let _ = Auth.auth().currentUser, let userData = defaults.object(forKey: Constants.UserDefaults.currentUser) as? Data, let user = try? JSONDecoder().decode(AppUser.self, from: userData) {
initialViewController = UIStoryboard.initialViewController(for: .main)
AppUser.setCurrent(user)
Client.config = .init(apiKey: Constants.Stream.apiKey,
appId: Constants.Stream.appId,
token: AppUser.current.userToken,
logsEnabled: true)
let streamUser = GetStreamActivityFeed.User(name: user.name, id: user.id)
streamUser.avatarURL = user.avatarURL
// ensures that the user exists on Stream (if not it will create it)
Client.shared.create(user: streamUser) { [weak initialViewController] result in
if let createdUser = try? result.get() {
Client.shared.currentUser = createdUser
// Refresh from here your view controller.
// Reload data in your timeline feed:
// flatFeedViewController?.reloadData()
}
}
} else {
initialViewController = UIStoryboard.initialViewController(for: .login)
}
window?.rootViewController = initialViewController
window?.makeKeyAndVisible()
}
}

Thread Error in Xcode w/ Swift When Launching New Screen

Once the user logs into my app, I set the window, set the rootViewController and makeKeyAndVisible and when I do I get a Thread 9: signal SIGABRT error. FWIW, I get a purple warning -[UIWindow initWithFrame:] must be used from main thread only when setting self.window = UIWindow(frame: UIScreen.main.bounds). See code below to see my setup.
The code dies with that error right here - self.window!.makeKeyAndVisible() in the launchWindow(aWindow: UIWindow?) function below in AppController.swift.
AppDelegate.swift
//
// AppDelegate.swift
import UIKit
import AWSCognitoAuth
import AWSSNS
import AWSCognitoIdentityProvider
import UserNotifications
import ESTabBarController_swift
import AWSMobileClient
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate {
var window: UIWindow?
var navigationController: UINavigationController?
var storyboard: UIStoryboard?
var loginViewController: LoginViewController?
var pool = AWSCognitoIdentityUserPool.init(forKey: "UserPool")
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
AppController.sharedInstance.enableCognitoClientWithAuthentication()
// setup logging
// pool.delegate = self
// AWSDDLog.sharedInstance.logLevel = .verbose
// let credentialsProvider = AWSCognitoCredentialsProvider(regionType: .USEast1,
// identityPoolId: Constants.APIKeys.AWSIdentityPoolID)
//
// // setup service configuration
// let serviceConfiguration = AWSServiceConfiguration(region: AWSRegionType.USEast1, credentialsProvider: credentialsProvider)
//
// // create pool configuration
// let poolConfiguration = AWSCognitoIdentityUserPoolConfiguration(clientId: Constants.APIKeys.AWSClientID,
// clientSecret: Constants.APIKeys.AWSSecret,
// poolId: Constants.APIKeys.AWSPoolID)
// AWSServiceManager.default().defaultServiceConfiguration = serviceConfiguration
// // initialize user pool client
// AWSCognitoIdentityUserPool.register(with: serviceConfiguration, userPoolConfiguration: poolConfiguration, forKey: "UserPool")
//
// pool.currentUser()?.getSession()
// fetch the user pool client we initialized in above step
//let pool = AWSCognitoIdentityUserPool(forKey: "UserPool")
let signedIn = AWSMobileClient.sharedInstance().isSignedIn
self.navigationController = UINavigationController()
if !signedIn {
navigationInit()
}
// } else {
// AppController.sharedInstance.showLoggedInStateAndReturn(true)
// }
//pool.delegate = self
self.window = UIWindow(frame: UIScreen.main.bounds)
AppController.sharedInstance.launchInWindow(aWindow: self.window)
return true
}
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
navigationInit()
return true
}
func navigationInit() {
let loginViewController = LoginViewController()
self.navigationController!.pushViewController(loginViewController, animated: false)
}
//MARK: Push Notification
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
/// Attach the device token to the user defaults
var token = ""
for i in 0..<deviceToken.count {
token = token + String(format: "%02.2hhx", arguments: [deviceToken[i]])
}
print(token)
UserDefaults.standard.set(token, forKey: "deviceTokenForSNS")
/// Create a platform endpoint. In this case, the endpoint is a
/// device endpoint ARN
let sns = AWSSNS.default()
let request = AWSSNSCreatePlatformEndpointInput()
request?.token = token
request?.platformApplicationArn = Constants.APIKeys.AWSSSNSARN
sns.createPlatformEndpoint(request!).continueWith(executor: AWSExecutor.mainThread(), block: { (task: AWSTask!) -> AnyObject? in
if task.error != nil {
print("Error: \(String(describing: task.error))")
} else {
let createEndpointResponse = task.result! as AWSSNSCreateEndpointResponse
if let endpointArnForSNS = createEndpointResponse.endpointArn {
print("endpointArn: \(endpointArnForSNS)")
Settings.setPushArn(endpointArnForSNS)
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "RegisteredForPush"), object: nil)
}
}
return nil
})
}
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
let visible = window?.visibleViewController()
if let data = userInfo["aps"] as? [AnyHashable: Any] {
if let route = data["route"] as? String {
switch route {
case "matchRequested":
var projectId = ""
if let project = data["projectId"] as? String {
projectId = project
}
var matchId = ""
if let match = data["matchId"] as? String {
matchId = match
}
let projectMatches = MatchRequestedViewController(withNotificationPayload: projectId, matchId: matchId)
visible?.navigationController?.pushViewController(projectMatches, animated: true)
case "projectDetails":
var projectId = ""
if let project = data["projectId"] as? String {
projectId = project
}
let projectMatches = ProjectDetailsViewController(withProject: TERMyProject(), orProjectId: projectId)
visible?.navigationController?.pushViewController(projectMatches, animated: true)
case "matched":
var projectId = ""
if let project = data["projectId"] as? String {
projectId = project
}
var matchId = ""
if let match = data["matchId"] as? String {
matchId = match
}
var originProject: TERMyProject = TERMyProject()
var matchedProject: TERMatchedProject = TERMatchedProject()
AppController.sharedInstance.AWSClient?.projectsGet(id:projectId).continueWith(block: { (task: AWSTask) -> Any? in
if let error = task.error {
print("Error: \(error)")
} else if let result = task.result {
if result is NSDictionary {
DispatchQueue.main.async {
let array = [result]
let parsedProject: [TERMyProject] = TERMyProject.parseFromAPI(array:array as! [NSDictionary])
for project in parsedProject {
originProject = project
}
// self.initialSetup()
}
AppController.sharedInstance.AWSClient?.projectsGet(id:matchId).continueWith(block: { (task: AWSTask) -> Any? in
if let error = task.error {
print("Error: \(error)")
} else if let result = task.result {
if result is NSDictionary {
DispatchQueue.main.async {
let array = [result]
let parsedProject: [TERMatchedProject] = TERMatchedProject.parseFromAPI(array:array as! [NSDictionary])
for project in parsedProject {
matchedProject = project
}
let projectMatches = MatchedViewController(withProject: originProject, match: matchedProject, isComplete: false)
visible?.navigationController?.pushViewController(projectMatches, animated: true)
}
}
}
return nil
})
}
}
return nil
})
default:
break
}
}
}
}
func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
print(error.localizedDescription)
}
// Called when a notification is delivered to a foreground app.
#available(iOS 10.0, *)
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: #escaping (UNNotificationPresentationOptions) -> Void) {
print("User Info = ",notification.request.content.userInfo)
completionHandler([.alert, .badge, .sound])
}
//MARK: Boiler-plate methods
func applicationWillResignActive(_ application: UIApplication) {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
}
func applicationDidEnterBackground(_ application: UIApplication) {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}
func applicationWillEnterForeground(_ application: UIApplication) {
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
}
func applicationDidBecomeActive(_ application: UIApplication) {
}
func applicationWillTerminate(_ application: UIApplication) {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}
}
extension AppDelegate: AWSCognitoIdentityInteractiveAuthenticationDelegate {
// func startPasswordAuthentication() -> AWSCognitoIdentityPasswordAuthentication {
// if (self.navigationController == nil) {
//
// self.navigationController = self.storyboard?.instantiateViewController(withIdentifier: "LoginViewController") as? UINavigationController
// }
//
// if (self.loginViewController == nil) {
// self.loginViewController = self.navigationController?.viewControllers[0] as? LoginViewController
// }
//
// DispatchQueue.main.async {
// self.navigationController!.popToRootViewController(animated: true)
// if (!self.navigationController!.isViewLoaded
// || self.navigationController!.view.window == nil) {
// self.window?.rootViewController?.present(self.navigationController!,
// animated: true,
// completion: nil)
// }
//
// }
// return self.loginViewController!
// }
}
// MARK:- AWSCognitoIdentityRememberDevice protocol delegate
extension AppDelegate: AWSCognitoIdentityRememberDevice {
func didCompleteStepWithError(_ error: Error?) {
}
func getRememberDevice(_ rememberDeviceCompletionSource: AWSTaskCompletionSource<NSNumber>) {
}
}
extension UIWindow {
func visibleViewController() -> UIViewController? {
if let rootViewController: UIViewController = self.rootViewController {
return UIWindow.getVisibleViewControllerFrom(vc: rootViewController)
}
return nil
}
class func getVisibleViewControllerFrom(vc:UIViewController) -> UIViewController {
switch(vc){
case is UINavigationController:
let navigationController = vc as! UINavigationController
return UIWindow.getVisibleViewControllerFrom( vc: navigationController.visibleViewController!)
break;
case is UITabBarController:
let tabBarController = vc as! UITabBarController
return UIWindow.getVisibleViewControllerFrom(vc: tabBarController.selectedViewController!)
break;
default:
if let presentedViewController = vc.presentedViewController {
//print(presentedViewController)
if let presentedViewController2 = presentedViewController.presentedViewController {
return UIWindow.getVisibleViewControllerFrom(vc: presentedViewController2)
}
else{
return vc;
}
}
else{
return vc;
}
break;
}
}
}
AppController.swift
func launchInWindow(aWindow: UIWindow?){
self.window = aWindow
self.initializeSDKs()
self.globalCustomization()
self.AWSUnAuthedClient.apiKey = Constants.APIKeys.AWSAPIKey
self.window!.rootViewController = self.showLoggedInStateAndReturn(true)
self.window!.makeKeyAndVisible()
}
func initializeSDKs() {
// Google places
GMSPlacesClient.provideAPIKey(Constants.APIKeys.GooglePlaces)
}
func globalCustomization() {
self.styleNavigationBar()
}
#discardableResult func showLoggedInStateAndReturn(_ shouldReturn: Bool) -> UIViewController? {
//AppController.sharedInstance.enableCognitoClientWithAuthentication()
//self.registerForPush()
self.tabBarController = ESTabBarController()
//tabBarController.delegate = delegate
self.tabBarController?.title = "Irregularity"
self.tabBarController?.tabBar.shadowImage = UIImage.image(with: UIColor("FFFFFF", alpha: 0.0)!)
self.tabBarController?.tabBar.backgroundImage = UIImage.image(with: UIColor("2A2A27")!)
self.tabBarController?.shouldHijackHandler = {
tabbarController, viewController, index in
if index == 1 {
return true
}
return false
}
self.tabBarController?.didHijackHandler = {
[weak tabBarController] tabbarController, viewController, index in
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
let newProjectNavCon = UINavigationController(rootViewController: NewProjectViewController())
newProjectNavCon.hero.isEnabled = true
newProjectNavCon.setNavigationBarHidden(true, animated: false)
newProjectNavCon.hero.navigationAnimationType = .fade
tabBarController?.present(newProjectNavCon, animated: true, completion: nil)
}
}
let centerVC = UINavigationController(rootViewController: HomeViewController())
let v1 = centerVC
let v2 = BaseViewController()
let v3 = UINavigationController(rootViewController: ProfileViewController())
v1.tabBarItem = ESTabBarItem.init(TabBarBouncesContentView(), title: "Projects", image: UIImage(named: "tabBar"), selectedImage: UIImage(named: "tabBar"))
v2.tabBarItem = ESTabBarItem.init(TabBarIrregularityContentView(), title: nil, image: UIImage(named: "tabBarPlusButton"), selectedImage: UIImage(named: "tabBarPlusButton"))
v3.tabBarItem = ESTabBarItem.init(TabBarBouncesContentView(), title: "Profile", image: UIImage(named: "tabBarProfile"), selectedImage: UIImage(named: "tabBarProfile"))
self.tabBarController?.setViewControllers([v1, v2, v3], animated: true)
if shouldReturn {
return self.tabBarController
} else {
self.window?.rootViewController = self.tabBarController
return nil
}
}
You should try to execute the piece of code in the main thread:
func launchInWindow(aWindow: UIWindow?){
self.window = aWindow
self.initializeSDKs()
self.globalCustomization()
self.AWSUnAuthedClient.apiKey = Constants.APIKeys.AWSAPIKey
DispatchQueue.main.async {
self.window!.rootViewController = self.showLoggedInStateAndReturn(true)
self.window!.makeKeyAndVisible()
}
}
Also, your question codebase looks a little bit weird in this part in AppDelegate.swift:
self.window = UIWindow(frame: UIScreen.main.bounds) //-- Purple warning here
AppController.sharedInstance.launchInWindow(aWindow: self.window)
return true
Looks like those three lines are not in a function but in class scope, which is weird and you shouldn't be able to compile it. Probably you pasted your code wrong?

Updating the label with current heart rate (swift and watch kit)

When you click on the button the current heart rate should be captured and the label should be updated. But, I do not understand why the label won't get updated with the heart rate value. I do not know what is wrong here.
I am still a very beginner in terms of swift and watch kit. I hope anyone can help me out.
Thank you very much.
Below you can see the code. I have also added the authorization part into the AppDelegate.swift file.
import WatchKit
import Foundation
import HealthKit
class InterfaceController: WKInterfaceController {
#IBOutlet weak var label: WKInterfaceLabel!
#IBOutlet weak var button: WKInterfaceButton!
var isRecording = false
//For workout session
let healthStore = HKHealthStore()
var session: HKWorkoutSession?
var currentQuery: HKQuery?
override func awake(withContext context: Any?) {
super.awake(withContext: context)
// Configure interface objects here.
}
override func willActivate() {
// This method is called when watch view controller is about to be visible to user
super.willActivate()
//Check HealthStore
guard HKHealthStore.isHealthDataAvailable() == true else {
print("Health Data Not Avaliable")
return
}
}
override func didDeactivate() {
// This method is called when watch view controller is no longer visible
super.didDeactivate()
}
#IBAction func btnPressed() {
if(!isRecording){
let stopTitle = NSMutableAttributedString(string: "Stop Recording")
stopTitle.setAttributes([NSAttributedString.Key.foregroundColor: UIColor.red], range: NSMakeRange(0, stopTitle.length))
button.setAttributedTitle(stopTitle)
isRecording = true
startWorkout() //Start workout session/healthkit streaming
}else{
let exitTitle = NSMutableAttributedString(string: "Start Recording")
exitTitle.setAttributes([NSAttributedString.Key.foregroundColor: UIColor.green], range: NSMakeRange(0, exitTitle.length))
button.setAttributedTitle(exitTitle)
isRecording = false
healthStore.end(session!)
label.setText("Heart Rate")
}
}
}
extension InterfaceController: HKWorkoutSessionDelegate{
func workoutSession(_ workoutSession: HKWorkoutSession, didChangeTo toState: HKWorkoutSessionState, from fromState: HKWorkoutSessionState, date: Date) {
switch toState {
case .running:
print(date)
if let query = heartRateQuery(date){
self.currentQuery = query
healthStore.execute(query)
}
//Execute Query
case .ended:
//Stop Query
healthStore.stop(self.currentQuery!)
session = nil
default:
print("Unexpected state: \(toState)")
}
}
func workoutSession(_ workoutSession: HKWorkoutSession, didFailWithError error: Error) {
//Do Nothing
}
func startWorkout(){
// If a workout has already been started, do nothing.
if (session != nil) {
return
}
// Configure the workout session.
let workoutConfiguration = HKWorkoutConfiguration()
workoutConfiguration.activityType = .running
workoutConfiguration.locationType = .outdoor
do {
session = try HKWorkoutSession(configuration: workoutConfiguration)
session?.delegate = self
} catch {
fatalError("Unable to create workout session")
}
healthStore.start(self.session!)
print("Start Workout Session")
}
func heartRateQuery(_ startDate: Date) -> HKQuery? {
let quantityType = HKObjectType.quantityType(forIdentifier: HKQuantityTypeIdentifier.heartRate)
let datePredicate = HKQuery.predicateForSamples(withStart: startDate, end: nil, options: .strictEndDate)
let predicate = NSCompoundPredicate(andPredicateWithSubpredicates:[datePredicate])
let heartRateQuery = HKAnchoredObjectQuery(type: quantityType!, predicate: predicate, anchor: nil, limit: Int(HKObjectQueryNoLimit)) { (query, sampleObjects, deletedObjects, newAnchor, error) -> Void in
//Do nothing
}
heartRateQuery.updateHandler = {(query, samples, deleteObjects, newAnchor, error) -> Void in
guard let samples = samples as? [HKQuantitySample] else {return}
DispatchQueue.main.async {
guard let sample = samples.first else { return }
let value = sample.quantity.doubleValue(for: HKUnit(from: "count/min"))
print("This line is executed!")
self.label.setText(String(UInt16(value))) //Update label
}
}
return heartRateQuery
}
}
import UIKit
import HealthKit
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
return true
}
func applicationWillResignActive(_ application: UIApplication) {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
}
func applicationDidEnterBackground(_ application: UIApplication) {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}
func applicationWillEnterForeground(_ application: UIApplication) {
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
}
func applicationDidBecomeActive(_ application: UIApplication) {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}
func applicationWillTerminate(_ application: UIApplication) {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}
let healthStore = HKHealthStore()
func applicationShouldRequestHealthAuthorization(_ application: UIApplication) {
healthStore.handleAuthorizationForExtension{
(success,error) in
}
}
}

My code for NSUserDefaults is not working

I am very new to swift. And need your help!
I want that, when the user logs in for the second time , the app should directly take it to the next view controller named CoreView. It should not ask for details, but I don't know why its not working. And it's asking for details everytime the app is launched. Please check the below code. I am not getting any sort of error too. Unless and until the app is killed or logged out, the user should be able to log in directly .
func pref_write()
{
// To write the data to NSUserDefaults
let prefs = NSUserDefaults.standardUserDefaults() // make a reference
print("OTP:\(OTP)")
// Adding values. Creating objects in prefs
prefs.setObject(OTP, forKey: "OTP")
print("check_OTP:\(check_OTP)")
prefs.setObject(U_ID, forKey: "U_ID")
print("Check_U_ID:\(check_U_ID)")
prefs.synchronize()
self.performSegueWithIdentifier("ContinueToCoreView", sender: self)
}
And in the viewDidLoad function:
override func viewDidLoad()
{
super.viewDidLoad()
//Read the data
self.performSegueWithIdentifier("ContinueToCoreView", sender: self)
pref_write()
let prefs = NSUserDefaults.standardUserDefaults()
check_OTP = prefs.objectForKey("OTP")!
check_U_ID = prefs.objectForKey("U_ID")!
prefs.objectForKey("U_ID")
print("prefs:\(prefs)")
prefs.synchronize()
}
Thanks!
Create a class as
class User_Details : NSObject
{
var user_id : String?
var user_otp : String?
var otp_verified : Bool?
init(u_id:String, otp:String?, verified:Bool)
{
super.init()
self.user_id = u_id
self.otp_verified = verified
self.user_otp = otp
}
}
In AppDelegate,
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool
{
navController = self.window?.rootViewController as? UINavigationController
if self.checkIfUserLoggedIn()
{
let user_details = NSUserDefaults.standardUserDefaults().objectForKey("user_details") as! User_Details
self.moveToNextScreen(user_details)
}
return true
}
//AppDelegate Class or in the class which is globally accessible
func pref_write_user(user_details : User_Details)
{
let prefs = NSUserDefaults.standardUserDefaults()
prefs.setObject(user_details, forKey: "user_details")
prefs.setBool(true, forKey: "is_user_login")
//After saving the OTP for current user, check for otp verified, move to OTP Screen
self.moveToNextScreen(user_details)
}
func moveToNextScreen(user_details : User_Details)
{
if user_details.otp_verified == false
{
// Move to OTP screen
let viewController = self.navController?.storyboard?.instantiateViewControllerWithIdentifier("otpScreen")
self.navController?.pushViewController(viewController!, animated: false)
}
else // Move to Home Screen
{
let viewController = self.navController?.storyboard?.instantiateViewControllerWithIdentifier("homeScreen")
self.navController?.pushViewController(viewController!, animated: false)
}
}
func logoutUser()
{
let prefs = NSUserDefaults.standardUserDefaults()
prefs.setObject(nil, forKey: "user_details")
prefs.setBool(false, forKey: "is_user_login")
}
func checkIfUserLoggedIn() -> Bool
{
let prefs = NSUserDefaults.standardUserDefaults()
if prefs.boolForKey("is_user_login")
{
if let _ = prefs.objectForKey("user_details")
{
return true
}
else
{
//User details not found for some reason, so setting the inital values and return false
self.logoutUser()
}
}
return false
}
Login Class :
Call the API for login by providing the basic credential, get the user_id and user_otp, save them to NSUserDefaults
func requestLoginToServer()
{
//Perform basic server action
....
//In Success Block write this
let appDel = UIApplication.sharedApplication().delegate as! AppDelegate
// pass the values as return by the server
let user_details = User_Details(u_id: "123", otp: "1234", verified: false)
appDel.pref_write_user(user_details)
appDel.moveToNextScreen(user_details)
}
Please try this way. I just rearranged your code.
First it will check the login credentials with in the didload method of initial view controller. If it not there it will call the method pref_write() . Please make sure that the values used in pref_write() method are not nil
override func viewDidLoad()
{
super.viewDidLoad()
let prefs:NSUserDefaults = NSUserDefaults.standardUserDefaults()
// You can give conditions for your need like if(prefs.valueForKey("U_ID") != nil))
// It will check the user defaults whether you already login
if(prefs.valueForKey("OTP") != nil) {
self.performSegueWithIdentifier("ContinueToCoreView", sender: self)
}
else{
pref_write()
}
}
// Make sure the Values are not nil
func pref_write()
{
// To write the data to NSUserDefaults
let prefs = NSUserDefaults.standardUserDefaults() // make a reference
print("OTP:\(OTP)")
// Adding values. Creating objects in prefs
prefs.setObject(OTP, forKey: "OTP")
print("check_OTP:\(check_OTP)")
prefs.setObject(U_ID, forKey: "U_ID")
print("Check_U_ID:\(check_U_ID)")
prefs.synchronize()
self.performSegueWithIdentifier("ContinueToCoreView", sender: self)
}
Hope its working...

Resources