Swift- Find location after shake motion ends - ios

I know how to do shake gesture and how to find location separately, however I don't know how to find location after the user shakes the device. I have xcode version 6.4.
Right now I have a motion ended function. And a showUserLocation function.
I would like to put a statement in motionEnded that calls the showUserLocation so that the location is found if the user shakes the phone.
override func motionEnded(motion: UIEventSubtype, withEvent event: UIEvent) {
?????
}
// Showing the user location
func showUserLocation(sender : AnyObject) {
let status = CLLocationManager.authorizationStatus()
//Asking for authorization to display current location
if status == CLAuthorizationStatus.NotDetermined {
locationManager.requestWhenInUseAuthorization()
} else {
locationManager.startUpdatingLocation()
}

Your question has nothing to do with a shake motion ending or calling location. Your question is one of basic syntax of Swift and general understanding of any programming language.
You code implementation for getting a location was "boxed" into an action func, perhaps a UIButton touchup inside where a sender was required as an argument. But you do not use the sender at all for getting location information, so it is easy to move this implementation to its own function, showUserLocationHelper so the action func can call getLocation and the shake motion can call get location.
The name of your action function: showUserLocation is poorly named it should have a name like btnShowUserLocationTouchUpInside, a long name but not confusing like what you named your action function and than tried to use in motionEnded.
override func motionEnded(motion: UIEventSubtype, withEvent event: UIEvent) {
//you can't pass the sender so couldn't call your showUserLocation action function,
//so move implementation of showUserLocation to showUserLocationHelper and now you can do what you need after shake event
showUserLocationHelper()
}
// Showing the user location
//this is an action function for some UI element like a UIButton and the sender is required to be passed for this function to fire thus, motionEnded cannot directly call it
//but you do not use sender at all
func showUserLocation(sender : AnyObject) {
showUserLocationHelper()
}
func showUserLocationHelper() {
let status = CLLocationManager.authorizationStatus()
//Asking for authorization to display current location
if status == CLAuthorizationStatus.NotDetermined {
locationManager.requestWhenInUseAuthorization()
} else {
locationManager.startUpdatingLocation()
}
}

Related

mapKit Apple: Bug with places' names and no focus map on user

I try to go from VC1 on VC2 with map by button1 through segue, and It has to focus pin on my location in the center of map and it has bug. it doesn't focus on user, but if I press button 2, go back and press button 1, it focuses on user. And I can't understand where is I need to fix it.
And the last bug: if I set huge value of regionInMeters, for e.g. 10000m, and I zoom, it shows names of places, streets with bug(image) in both cases, when I try to use this method with user. If I set low - 500, it shows correct close in these cases, but just location around user, if I switch there in another city and zoom in, it still will have this bug. And the same story with focus on place. On huge height - country names, cities, rivers are correct everywhere .
let annotationIdentifire = "annotationIdentifire"
let locationManager = CLLocationManager()
let regionInMeters = 500.00
var incomeSegueIdentifire = ""
override func viewDidLoad() {
super.viewDidLoad()
addressLabel.text = ""
mapView.delegate = self
setupMapView()
checkLocationServices()
}
private func checkLocationServices(){
if CLLocationManager.locationServicesEnabled() {
setupLocationManager()
checkLocationAuthorization()
} else{...}
private func checkLocationAuthorization() {
switch CLLocationManager.authorizationStatus() {
case .authorizedWhenInUse:
mapView.showsUserLocation = true
if incomeSegueIdentifire == "getAddress" { showUserLocation() }
break
//other cases
}
Also I have another button2 with segue on VC1 which opens VC2 with map and it focuses on place, with address, which I set, and in this case I have button3 on map, which focuses on me correct:
#IBAction func centerViewInUserLocation() {
showUserLocation()
}
There is method which I use to focus on user.
private func showUserLocation() {
if let location = locationManager.location?.coordinate {
let region = MKCoordinateRegion(center: location,
latitudinalMeters: regionInMeters,
longitudinalMeters: regionInMeters)
mapView.setRegion(region, animated: true)
}
}
Value of incomeSegueIdentifire I set in another class
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard let identifire = segue.identifier, let mapVC = segue.destination as? MapViewController else { return }
mapVC.incomeSegueIdentifire = identifire
}
I din't find how to fix bug with places' names, but I fixed the main bug(when open map by segue it has to focus on user, but it's not).
I get mistake:Could not retrieve region info. And tried to find the answer for this case, but didn't find. Found couple cases with answers about this mistake and there said, that code is OK, but still mistake, and the main answer/advice - try to run it on your real device, may be it's correct, I don't have iphone/ipad. But for simulator I fixed it by calling this method asynchronously with delay, and it works for me)
private func checkLocationAuthorization() {
switch CLLocationManager.authorizationStatus() {
case .authorizedWhenInUse:
mapView.showsUserLocation = true
if incomeSegueIdentifire == "getAddress" { DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
self.showUserLocation()
}
}
//other cases
}
}
But still, if you have any ideas with these issues, I'll be glad to know))

iOS: app freezes when dictionary key contains a period?

The simplified code below freezes when dictionary keys contain a period. The app doesn't crash, just ignores gestures and touches. For instance, the touchesBegan function in the UIViewController doesn't get called.
Replacing the period with another character like a dash allows the app to function.
Are Swift dictionaries not allowed to contain periods in keys?
let name = "TSC-Body.043" // App fails with this "name" definition
// let name = "TSC-Body-043" // App works with this "name" definition
var test = [String:Any]()
if test[name] == nil {
test[name] = UIColor.black
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
print("Never gets called with a period in keys")
}

How to detect all touches in Swift 2

I'm trying to create a timeout function for an app I'm develop using Swift 2 but in swift 2, you can put this code in the app delegate and it works but it does not detect any keyboard presses, button presses, textfield presses, and etc:
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
super.touchesBegan(touches, withEvent: event);
let allTouches = event!.allTouches();
if(allTouches?.count > 0) {
let phase = (allTouches!.first as UITouch!).phase;
if(phase == UITouchPhase.Began || phase == UITouchPhase.Ended) {
//Stuff
timeoutModel.actionPerformed();
}
}
}
Before swift 2, I was able to have the AppDelegate subclass UIApplication and override sendEvent: like this:
-(void)sendEvent:(UIEvent *)event
{
[super sendEvent:event];
// Only want to reset the timer on a Began touch or an Ended touch, to reduce the number of timer resets.
NSSet *allTouches = [event allTouches];
if ([allTouches count] > 0) {
// allTouches count only ever seems to be 1, so anyObject works here.
UITouchPhase phase = ((UITouch *)[allTouches anyObject]).phase;
if (phase == UITouchPhaseBegan || phase == UITouchPhaseEnded)
[[InactivityModel instance] actionPerformed];
}
}
The code above works for every touch but the swift equivalent only works when a view does not exist above that UIWindow's hierarchy?
Does anyone know a way to detect every touch in the application?
As I have something similar in my application, I just tried to fix it:
override sendEvent in UIWindow - doesn't work
override sendEvent in delegate - doesn't work
So the only way is to provide custom UIApplication subclass. My code so far (works on iOS 9) is:
#objc(MyApplication) class MyApplication: UIApplication {
override func sendEvent(event: UIEvent) {
//
// Ignore .Motion and .RemoteControl event
// simply everything else then .Touches
//
if event.type != .Touches {
super.sendEvent(event)
return
}
//
// .Touches only
//
var restartTimer = true
if let touches = event.allTouches() {
//
// At least one touch in progress?
// Do not restart auto lock timer, just invalidate it
//
for touch in touches.enumerate() {
if touch.element.phase != .Cancelled && touch.element.phase != .Ended {
restartTimer = false
break
}
}
}
if restartTimer {
// Touches ended || cancelled, restart auto lock timer
print("Restart auto lock timer")
} else {
// Touch in progress - !ended, !cancelled, just invalidate it
print("Invalidate auto lock timer")
}
super.sendEvent(event)
}
}
Why there's #objc(MyApplication). That's because Swift mangles names in a different way then Objective-C and it just says - my class name in Objective-C is MyApplication.
To make it working, open your info.plist and add row with Principal class key and MyApplication value (MyApplication is what's inside #objc(...), not your Swift class name). Raw key is NSPrincipalClass.
UIWindow also has a sendEvent method that you can override. That would allow you to track the time since the last screen touch. Swift 4:
class IdlingWindow: UIWindow {
/// Tracks the last time this window was interacted with
var lastInteraction = Date.distantPast
override func sendEvent(_ event: UIEvent) {
super.sendEvent(event)
lastInteraction = Date()
}
}
If you're using a storyboard, you can load it in didFinishLaunchingWithOptions:
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: IdlingWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
window = IdlingWindow(frame: UIScreen.main.bounds)
window?.rootViewController = UIStoryboard.init(name: "Main", bundle: nil).instantiateInitialViewController()
window?.makeKeyAndVisible()
return true
}
:
}
extension UIApplication {
/// Conveniently gets the last interaction time
var lastInteraction: Date {
return (keyWindow as? IdlingWindow)?.lastInteraction ?? .distantPast
}
}
Now elsewhere in your app, you can check for inactivity like this:
if UIApplication.shared.lastInteraction.timeIntervalSinceNow < -2 {
// the window has been idle over 2 seconds
}
#markiv answer works like a charm, with two issues:
keyWindow
"'keyWindow' was deprecated in iOS 13.0: Should not be used for applications that support multiple scenes as it returns a key window across all connected scenes"
Can be solved like this:
let kw = UIApplication.shared.windows.filter {$0.isKeyWindow}.first
found here
see answer of #matt
AppDelegate - I get this message:
The app delegate must implement the window property if it wants to use a main storyboard file
One can subclass UIWindow - found the answer here. Combine this with the IdlingWindow class.
Message is gone.
var customWindow: IdlingWindow?
var window: UIWindow? {
get {
customWindow = customWindow ?? IdlingWindow(frame: UIScreen.mainScreen().bounds)
return customWindow
}
set { }
}

Delegate Call back method returns variable slow which causes variable nil while accessing from another class?

In my app delegate class, i am trying to retrieve user Current Location from another class using delegate. This retreived User Curren location will be used in many parts of my application.So ,i have set it here in AppDelegate Class
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var helperLocation:HelperLocationManager?
var currentLocation:CLLocation?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
//get the current location and set it so all other screens can use it
self.helperLocation = HelperLocationManager()
self.helperLocation?.delegate = self
return true
}
}
extension AppDelegate: SendLocationDelegate{
func sendCoOrdinates(loccoordinate:CLLocation, placemark:CLPlacemark){
currentLocation = loccoordinate
}
}
And this is what seems to be my HelperLocationManager Class
protocol SendLocationDelegate{
func sendCoOrdinates(coordinates:CLLocation,placemark:CLPlacemark)
}
class HelperLocationManager: NSObject {
var locationManager = CLLocationManager()
var delegate:SendLocationDelegate?
override init() {
super.init()
var code = CLLocationManager.authorizationStatus()
if code == CLAuthorizationStatus.NotDetermined {
locationManager.requestAlwaysAuthorization()
locationManager.requestWhenInUseAuthorization()
}
locationManager.delegate = self
}
}
extension HelperLocationManager: CLLocationManagerDelegate{
func locationManager(manager: CLLocationManager!, didChangeAuthorizationStatus status: CLAuthorizationStatus) {
switch status {
case CLAuthorizationStatus.Restricted:
println( "Restricted Access to location")
case CLAuthorizationStatus.Denied:
println( "User denied access to location please turn on the location")
// UIApplication.sharedApplication().openURL(NSURL(string: UIApplicationOpenSettingsURLString)!)
//may be open here settings page and say to turn on the setting location services
default:
locationManager.startUpdatingLocation()
}
}
func locationManager(manager: CLLocationManager!, didUpdateLocations locations: [AnyObject]!) {
var locValue = locations.last as! CLLocation
locationManager.stopUpdatingLocation()
CLGeocoder().reverseGeocodeLocation(manager.location, completionHandler: {(placemarks,error)-> Void in
if (error != nil) {
println("Reverse geocoder failed with error" + error.localizedDescription)
return
}
if placemarks.count > 0 {
let pm = placemarks[0] as! CLPlacemark
self.delegate?.sendCoOrdinates(locValue,placemark: pm)
} else {
println("Problem with the data received from geocoder")
}
})
}
func locationManager(manager: CLLocationManager!, didFailWithError error: NSError!) {
println("Your error is ", error.localizedDescription)
}
}
I made my call back method to trigger if there is any change in the user location...
Everything is fine. HelperLocationManager class sends the current location to the method sendCoOrdinatesthat is implemented in AppDelegate And I have set the current location and now i am accessing these location from presentedViewController as
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
pickUpDistanceLocation = appDelegate.currentLocation
My problem is when i try to access the value very fast enough in another class during the time interval the delegate call back method doesnot send me my current Location.I get nil in this case.but if i wait 2-3 sec and go to another class i get the value from delegate.
Can anyone explain me what am i doing wrong?
This is an architectural issue - you say:
when i try to access the value very fast enough in another class during the time interval the delegate call back method does not send me my current Location
You have to invert that - rather than checking the current location, with the risk of not having one because it's not been obtained yet, you should let the HelperLocationManager notify when it has a location (the hollywood principle: don't call me, I'll call you).
This can be done in different ways:
using the delegation pattern
using an event bus (which can be implemented with NSNotificationCenter)
using callbacks
There are of course many other ways to achieve the same result.
The delegation pattern is probably not the best solution when there are more than one observer.
I would use the callback way, with a subscriber registering to location updates by providing a closure to HelperLocationManager.
HelperLocationManager can store all callbacks into an array, and invoke each of them when a location update is available. Optionally, it can invoke a closures right after registration, if a location is already available.
Last, the subscriber must be able to unsubscribe, so HelperLocationManager should expose a method which removes a callback from its internal list.
This is just an idea - as said, it can be done in several different ways, the common rule is to just invert how the location is passed.
Note: I would make HelperLocationManager a singleton, and remove it from AppDelegate. If I want to use HelperLocationManager, I should contact it directly, instead of having to access through a 3rd party (the app delegate).

'Set<NSObject>' does not have a member named 'anyObject." - Xcode 6.3

I'm checking to see if an element has been selected.
func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent)
{
// First, see if the game is in a paused state
if !gamePaused
{
// Declare the touched symbol and its location on the screen
let touch = touches.anyObject! as? UITouch
let location = touch.locationInNode(symbolsLayer)
And this had previously compiled fine in Xcode 6.2 but with a 6.3 update, the line "let touch = touches.anyObject! as? UITouch" is throwing the error:
'Set' does not have a member named 'anyObject'
I've read through many similar question, but I can't seem to wrap my head around "To use the value, you need to “unwrap” it first." Especially because the answers seem to focus on notifications.
Thank you so much.
W
let touch = touches.first as? UITouch
.first can allow you to access first object of UITouch.
Since Xcode 6.3 uses an updated version of Swift (1.2) you need to convert your old code into Swift 1.2 (Edit -> convert -> To lastest Swift).
Swift 1.2, uses Set’s (new in Swift) instead of using NSSet’s (old one in Objective-C). Thus the touchbegan function also changes its parameters from NSSet to Set.
For more info, refer this
This would check for multiple touches in symbolsLayer
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent)
{
// First, see if the game is in a paused state
if !gamePaused
{
// Declare the touched symbol and its location on the screen
for touch: AnyObject in touches {
let location = (touch as! UITouch).locationInNode(symbolsLayer)
}
}
}

Resources