I am just getting started with SWIFT programming on iOS. However I have good experience in other languages such as JAVA, C# and PHP. That said, I am running into a little bit of a problem. I am creating a simple application that will show a user information based on their geographical location. I am following the tutorial listed here.
However, since a lot of things have changed between Swift 2.x and 3.x I have had to modify his code. Not too hard and so far I have gotten it to compile. However, I am never asked by the application to allow me to use the device's location.
I have followed the directions Apple listed here about putting "NSLocationAlwaysUsageDescription" in the Info.plist. However I am never prompted. The relevant piece of code that should be showing this alert is listed below.
Any help would be greatly appreciated. Thanks.
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// 1. status is not determined
if CLLocationManager.authorizationStatus() == .notDetermined {
locationManager.requestAlwaysAuthorization()
}
// 2. authorization were denied
else if CLLocationManager.authorizationStatus() == .denied {
let controller = UIAlertController(title: "Error!", message: "Location services were previously denied. Please enable location services for this app in Settings.", preferredStyle: .alert)
let alertAction = UIAlertAction(title: "Dismiss", style: .destructive) { (action) in
print("Dismiss button tapped!")
}
controller.addAction(alertAction)
}
// 3. we do have authorization
else if CLLocationManager.authorizationStatus() == .authorizedAlways {
locationManager.startUpdatingLocation()
}
}
Your code is working fine you just need to present your alert:
self.present(controller, animated: true, completion: nil)
And also take a look at my answer here how you can use switch for this types of code.
Related
I'm learning iOS development and faced strange problem. To get user permission for using location in viewController I'm defining location manager and requesting requestWhenInUseAuthorization().
Like this:
class ViewController: UIViewController, CLLocationManagerDelegate {
let lm = CLLocationManager()
override func viewDidLoad() {
super.viewDidLoad()
lm.requestWhenInUseAuthorization()
}
}
Then, in plist I'm setting proper text for permission dialog, like this:
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>We need your location</string>
<key>NSLocationAlwaysUsageDescription</key>
<string>We need your location</string>
<key>NSLocationUsageDescription</key>
<string>We need your location</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>We need your location</string>
Everything works fine till I'm trying to change text for permission dialog. After changing it in plist I'm recompiling it but nothing changes in the app, I'm receiving dialog with old text. So my question - how can I change it?
P.S. Xcode 10.3 & iOS 12.4
Go to the ViewController.swift file and add the following line to import the CoreLocation framework.
import CoreLocation
Conform the ViewController class to the CLLocationManagerDelegate protocol. Change the class declaration line to
class ViewController: UIViewController, CLLocationManagerDelegate {
Add the following property
let locationMgr = CLLocationManager()
The CLLocationManager is the object that will give you the GPS coordinates. Next, implement the getLocation(_:) method.
#IBAction func getLocation(_ sender: Any) {
// 1
let status = CLLocationManager.authorizationStatus()
switch status {
// 1
case .notDetermined:
locationManager.requestWhenInUseAuthorization()
return
// 2
case .denied, .restricted:
let alert = UIAlertController(title: "Location Services disabled", message: "Please enable Location Services in Settings", preferredStyle: .alert)
let okAction = UIAlertAction(title: "OK", style: .default, handler: nil)
alert.addAction(okAction)
present(alert, animated: true, completion: nil)
return
case .authorizedAlways, .authorizedWhenInUse:
break
}
// 4
locationManager.delegate = self
locationManager.startUpdatingLocation()
}
The authorizationStatus returns the current authorisation status.
The when in use authorisation get location updates while the app is in the foreground
When location services is disabled, the user will be shown an alert
Send location updates to the delegate, which is the View Controller.
Next, implement the CLLocationManager delegate methods.
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
if let currentLocation = locations.last {
print("Current location: \(currentLocation)")
}
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print(error)
}
The current location is printed to the console.
An Error is generated and displayed when the location can't get updated.
To enable the permissions for location updates while the app is running a special key is needed. Open info.plist. Right-click and select Add Row. Enter the following Values.
In Info.plist
<key>NSLocationWhenInUse</key>
<string>This app needs access to your Location</string>
Build and Run the project, When the app ask for permission
Looks like I've found where problem was. But firs things first. If you are trying to localize your app - read carefully what xcode asks you. In my case it was deleting Info.plist file from root directory when I tried to localize it, then (obviously) xcode informed me that it can't make a build because of it can't find Info.plist file (which was deleted from root directory).
Solution
Move Info.plist from Base.lproj directory;
Make as written in this article;
Keys in strings file should be exactly as they are named in system. This won't work:
"SomKey" = "We need you location";
But this would:
"NSLocationWhenInUseUsageDescription" = "We need you location";
I had added share extension to upload file.
But i want to stop open share extension when user is not logged in application and show alert similar like Messenger application from facebook.
Facebook messenger app
How can i do this
Note : I know how to do check is User logged in or not using app groups. But I want to check before open the share extension and show alert. in my case its first open the share extension and then i am showing alert. I want to check before open the share extension
Rather than directly open your custom view for share extension you could use alert first to check if user logged in or not, then if user has logged in, you can proceed to present your custom view with animation.
you can do this by adding below method on ShareViewController: SLComposeServiceViewController.
override func viewDidLoad() {
// check if user logged in or not here and if not below code will be executed.
self.loginAlertController = UIAlertController(title: "Please launch application from the home screen before continuing.", message: nil, preferredStyle: .alert)
let onOk = UIAlertAction(title: "OK", style: .destructive) { alert in
self.extensionContext?.cancelRequest(withError: NSError(domain: "loging", code: 0, userInfo: nil))
}
loginAlertController!.addAction(onOk)
present(loginAlertController!, animated: true, completion: nil)
}
You should make use of App groups to communicate between the main app and extension app.(Sharing Data: NSUserDefaults and App Groups
)
You can add data in main app like this :-
let mySharedDefaults = UserDefaults(suiteName: "group.yourValue")
mySharedDefaults?.set(false, forKey: "isLoggedIn")
Then you can get data like this in your extension
let mySharedDefaults = UserDefaults(suiteName: "group.yourValue")
if let isLoggedIn = mySharedDefaults?.value(forKey: "isLoggedIn") as? Bool {
if !isLoggedIn {
showAlert()
}
}
Refer this for implementing app groups
I have a view that:
Creates an observer for UIApplicationDidBecomeActiveNotification with invokes a selector
Sequentially asks the user for permissions to: use the camera, location & receiving push notifications.
The view has three UIButtons with state depending on each permission state, which navigate the user to settings if permissions for anything were rejected
Tapping a button which represents a permission with rejected state navigates the user to settings
Once each alert hides, using the observer action, next alert is triggered and all button states are updated to reflect any changes
Once all permissions are granted it pushes next view with the rest of the signup/in flow.
The problem is: on some devices, when running the app from a clean state (app removed and reinstalled), permissions for location & notifications are set to rejected by default, as if the user was presented an alert that was rejected.
I couldn't pinpoint any rational issue behind this, except for leftover settings from some outdated build that don't get deleted when installing a new one. This view seems to be the only place that can possibly trigger these alerts.
Did anyone have a similar issue and can suggest anything?
I would suggest you to try to check for states of location services and notification services before asking user to use it. Since if user is going to disable these the moment you ask him for permission, he will need to go to the settings and enable it there. You should try to detect if user has disabled location/notification/camera.
For camera use:
func accessToCamera(granted: #escaping (() -> Void)) {
if UIImagePickerController.isSourceTypeAvailable(.camera) {
let status = AVCaptureDevice.authorizationStatus(forMediaType: AVMediaTypeAudio)
if status == .authorized {
granted()
} else if status == .denied {
self.cameraPermissionAlert()
} else if status == .notDetermined {
AVCaptureDevice.requestAccess(forMediaType: AVMediaTypeVideo, completionHandler: { (accessAllowed) in
if accessAllowed {
granted()
} else {
self.cameraPermissionAlert()
}
})
} else if status == .restricted {
self.cameraPermissionAlert()
}
} else {
print("Camera not available on this device")
}
}
func cameraPermissionAlert() {
let alert = UIAlertController(title: "Access to camera not available", message: "Please enable access to camera in order to use this feature", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Settings", style: .default, handler: { (action) in
if let url = URL(string: UIApplicationOpenSettingsURLString) {
if UIApplication.shared.canOpenURL(url) {
UIApplication.shared.open(url, options: [:], completionHandler: nil)
}
}
}))
alert.addAction(UIAlertAction(title: "Cancel", style: .default, handler: nil))
if let top = UIApplication.topViewController() { // This is extension to UIApplication that finds top view controller and displays it
top.present(alert, animated: true, completion: nil)
}
}
For remote notifications you can use something like this:
Determine on iPhone if user has enabled push notifications
And for location services:
Check if location services are enabled
In both of these cases you can detect if this is disabled or not by user and present user with alert controller that has open settings functionality.
Problem:
I am currently facing a problem with developing an iOS Mobile Application in Swift that utilizes:
BTLE: Connecting to a peripheral device and sending/receiving data to/from it.
Networking: If the peripheral is connected to a network (wireless and/or ethernet), then the communication over BTLE "could" instead happen over the network.
Model-View-ViewModel architecture
RxSwift
About the App:
It starts with a Bluetooth Setup view, which walks the user through the process of pairing with the peripheral device (disjoint from the TabBarController).
After successfully pairing with the device, all configuration is requested by the iOS App from the device, which is sent as JSON.
This JSON contains the different Model information (programming) that the App displays to the user for manipulation and needs to be stored in a array somehow in a Singleton manor to where a view-model can request any index for displaying to the user.
After all the data is received, the Bluetooth View dismisses and the TabBarView's are presented.
Current Examples:
A good example to relate this App to would be the Apple Watch and the correlating iOS App that allows you to configure everything. I am having to do somewhat the same concept.
Another good example app from this blog post where they are doing something similar to what I am trying to achieve. The difference I am running into though, is their dependency injection setup for MVVM (as well as other similar examples). I've used a storyboard, where as they have programmatically instantiated their view controllers in the AppDelegate.
And my problem...
How can I pass the data (efficiently) from BluetoothView to TabBarView without NSNotifications or PrepareForSegues? Keeping in mind that I am intending to use the library RxSwift for asynchronous event handling and event/data streams. I am trying to keep this App as stateless as possible.
Are the Servers in this blog post a good practice for retrieving view-models and/or updating them?
I find that, when using RxSwift, the "view-model" ends up being a single pure function that takes observable parameters from the input UI parameters and returns observables that are then bound to the output UI elements.
Something that really helped me wrap my head around Rx was the tutorial videos for cycle.js.
As for your specific conundrum...
What you are doing doesn't have to be "forward" movement. Look at it this way... The TabBarView needs some data, and it doesn't care where that data comes from. So give the TabBarView access to a function that returns an observable which contains the necessary data. That closure will present the Bluetooth View, make the connection, get the necessary data and then dismiss the Bluetooth View and call onNext with the required data.
Looking at this gist might help get across what I'm talking about. Granted the gist uses PromiseKit instead of RxSwift, but the same principle can be used (instead of fulfill, you would want to call onNext and then onCompletion.) In the gist, the view controller that needs the data simply calls a function and subscribes to the result (in this case, the result contains a UIImage.) It is the function's job to determine what image sources are available, ask the user which source they want to retrieve the image from and present the appropriate view controller to get the image.
The current contents of the gist are below:
//
// UIViewController+GetImage.swift
//
// Created by Daniel Tartaglia on 4/25/16.
// Copyright © 2016 MIT License
//
import UIKit
import PromiseKit
enum ImagePickerError: ErrorType {
case UserCanceled
}
extension UIViewController {
func getImage(focusView view: UIView) -> Promise<UIImage> {
let proxy = ImagePickerProxy()
let cameraAction: UIAlertAction? = !UIImagePickerController.isSourceTypeAvailable(.Camera) ? nil : UIAlertAction(title: "Camera", style: .Default) { _ in
let controller = UIImagePickerController()
controller.delegate = proxy
controller.allowsEditing = true
controller.sourceType = .Camera
self.presentViewController(controller, animated: true, completion: nil)
}
let photobinAction: UIAlertAction? = !UIImagePickerController.isSourceTypeAvailable(.PhotoLibrary) ? nil : UIAlertAction(title: "Photos", style: .Default) { _ in
let controller = UIImagePickerController()
controller.delegate = proxy
controller.allowsEditing = false
controller.sourceType = .PhotoLibrary
self.presentViewController(controller, animated: true, completion: nil)
}
let cancelAction = UIAlertAction(title: "Cancel", style: .Cancel, handler: nil)
let alert = UIAlertController(title: nil, message: nil, preferredStyle: .ActionSheet)
if let cameraAction = cameraAction {
alert.addAction(cameraAction)
}
if let photobinAction = photobinAction {
alert.addAction(photobinAction)
}
alert.addAction(cancelAction)
let popoverPresentationController = alert.popoverPresentationController
popoverPresentationController?.sourceView = view
popoverPresentationController?.sourceRect = view.bounds
presentViewController(alert, animated: true, completion: nil)
let promise = proxy.promise
return promise.always {
self.dismissViewControllerAnimated(true, completion: nil)
proxy.retainCycle = nil
}
}
}
private final class ImagePickerProxy: NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
let (promise, fulfill, reject) = Promise<UIImage>.pendingPromise()
var retainCycle: ImagePickerProxy?
required override init() {
super.init()
retainCycle = self
}
#objc func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : AnyObject]) {
let image = (info[UIImagePickerControllerEditedImage] as? UIImage) ?? (info[UIImagePickerControllerOriginalImage] as! UIImage)
fulfill(image)
}
#objc func imagePickerControllerDidCancel(picker: UIImagePickerController) {
reject(ImagePickerError.UserCanceled)
}
}
I have noticed the following with a plain, completely new project in Xcode.
If, in the ViewController.swift file I import CoreLocation, and then in the viewDidLoad method I add...
print(CLLocationManager.locationServicesEnabled())
..., when the app runs in simulator Xcode prints out true. I would have thought that location services would be disabled by default, but as you can see for yourself, the opposite is true. If I wanted I could add some more code to gather location information about the user, and all this without ever having to ask for permission.
Can anybody explain why this is?
As far as I know, CLLocationManager.locationServicesEnabled() will return whether location Services are enabled on the device, not just for that one app. So even if location Services are disabled for that app, if they are enabled for the device, I think that will still return true, as per the documentation: https://developer.apple.com/library/ios/documentation/CoreLocation/Reference/CLLocationManager_Class/#//apple_ref/occ/clm/CLLocationManager/locationServicesEnabled
In my app, I set it up like this:
//check if location services are enabled at all
if CLLocationManager.locationServicesEnabled() {
switch(CLLocationManager.authorizationStatus()) {
//check if services disallowed for this app particularly
case .Restricted, .Denied:
print("No access")
var accessAlert = UIAlertController(title: "Location Services Disabled", message: "You need to enable location services in settings.", preferredStyle: UIAlertControllerStyle.Alert)
accessAlert.addAction(UIAlertAction(title: "Okay!", style: .Default, handler: { (action: UIAlertAction!) in UIApplication.sharedApplication().openURL(NSURL(string:UIApplicationOpenSettingsURLString)!)
}))
presentViewController(accessAlert, animated: true, completion: nil)
//check if services are allowed for this app
case .AuthorizedAlways, .AuthorizedWhenInUse:
print("Access! We're good to go!")
//check if we need to ask for access
case .NotDetermined:
print("asking for access...")
manager.requestAlwaysAuthorization()
}
//location services are disabled on the device entirely!
} else {
print("Location services are not enabled")
}
Good luck!
Swift 3.1 function to return status and error message
func isLocationEnabled() -> (status: Bool, message: String) {
if CLLocationManager.locationServicesEnabled() {
switch(CLLocationManager.authorizationStatus()) {
case .notDetermined, .restricted, .denied:
return (false,"No access")
case .authorizedAlways, .authorizedWhenInUse:
return(true,"Access")
}
} else {
return(false,"Turn On Location Services to Allow App to Determine Your Location")
}
}