Hello All: i have a function that hit API to download data (LAT,LONG)and it works 100% fine. but the problem is i want to recall this function every 5 second and after i add a timer to do that every time i try to run it run very well and after 5 second i got crash
unrecognized selector sent to instance 0x7ffa4a51cb00
2018-07-20 11:05:31.191467+0200 Interactive Bus[684:6752] ***
Terminating app due to uncaught exception 'NSInvalidArgumentException',
reason: '-[Interactive_Bus.MapVC
downloadBusDataWithUserId:warning:delegate:completed:]: unrecognized
selector sent to instance 0x7ffa4a51cb00'
i try to clean then build and there is no Code Error i have no idea why this crash happens
this is my Model Class
// busLocationModel.swift
// Interactive Bus
import UIKit
import Alamofire
import SwiftyJSON
class busLocationModel {
var route_pins: String?
var busLatitude: Double?
var busLongitude: Double?
#objc func downloadBusData(userId: String,warning: #escaping (String,String,String) -> Void, delegate : MapVC ,completed : #escaping DownloadCompleted) {
let Parameters = ["parent_id": userId]
print(#function)
Alamofire.request(busLocationUrl, method: .get, parameters: Parameters).responseJSON { (response) in
switch response.result {
case .failure(let error):
print(error)
let alertControllerTitle = "Warning"
let actionButtonTitle = "Ok"
let alertMessage = "Some Thing Went Wrong Please Try Agin Later "
return warning(alertControllerTitle, actionButtonTitle, alertMessage)
case .success(let Value):
let json = JSON(Value)
//print(json)
let status = json["status"].boolValue
if status != false {
for locations in json["data"].arrayValue {
let busPins = locations["route_pins"].stringValue
let bus_lat = locations["bus_lat"].doubleValue
let bus_long = locations["bus_long"].doubleValue
delegate.busPins = busPins
delegate.currentBusLate = bus_lat
delegate.currentBusLong = bus_long
print(delegate.busPins ?? "HH")
print("the bus lat is \(bus_lat)")
print("the bus long is \(bus_long)")
}
}
}
completed()
}
}
}
and my Const is :
typealias DownloadCompleted = () -> ()
and My MapVC is :
//
import UIKit
import MapKit
import CoreLocation
class MapVC: UIViewController {
#IBOutlet weak private var busMapView: MKMapView!
var locationManager = CLLocationManager()
//var locationManager: CLLocationManager!
let authorizationStatus = CLLocationManager.authorizationStatus()
fileprivate let regionRadius: Double = 1000 //Meter's From UP,Right,Down and Left
fileprivate var busInfoObject: busLocationModel!
var busPins: String!
var currentBusLate: CLLocationDegrees?
var currentBusLong: CLLocationDegrees?
var callFuncTimer: Timer!
override func viewDidLoad() {
super.viewDidLoad()
locationManager.delegate = self
busMapView.delegate = self
busInfoObject = busLocationModel()
let myActivity = CreatActivityIndicator()
busInfoObject.downloadBusData(userId: "366", warning: DisplayAlertMessage, delegate: self) {
self.drawLine()
self.RemoveActivityIndicator(ActivityIndicator: myActivity)
guard let latitude = self.currentBusLate else { return }
guard let longitude = self.currentBusLong else { return }
let Location = CLLocation(latitude: latitude, longitude: longitude)
self.centerMapOnBusLocation(location: Location)
self.callFuncTimer = Timer.scheduledTimer(timeInterval: 5, target: self, selector: #selector(self.busInfoObject.downloadBusData(userId:warning:delegate:completed:)), userInfo: nil, repeats: true)
}
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(true)
configureLocationServices()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(true)
callFuncTimer.invalidate()
}
#IBAction func centerMapBtnPressed(_ sender: Any) {
if authorizationStatus == .authorizedAlways {
guard let latitude = self.currentBusLate else { return }
guard let longitude = self.currentBusLong else { return }
let Location = CLLocation(latitude: latitude, longitude: longitude)
centerMapOnBusLocation(location: Location)
}
}
}
extension MapVC: MKMapViewDelegate {
fileprivate func centerMapOnBusLocation(location: CLLocation) {
//guard let currtntLocationCoordinate = locationManager.location?.coordinate else { return }
let coordinateRegion = MKCoordinateRegionMakeWithDistance(location.coordinate , regionRadius * 2.0, regionRadius * 2.0)
busMapView.setRegion(coordinateRegion, animated: true)
}
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
if (overlay is MKPolyline) {
let pr = MKPolylineRenderer(overlay: overlay)
pr.strokeColor = UIColor.blue
pr.lineWidth = 5
return pr
}
return MKPolylineRenderer()
}
}
extension MapVC {
func drawLine() {
let coordinates = busPins.components(separatedBy: "#").dropFirst().map { (pin) -> CLLocationCoordinate2D in
let latLng = pin.components(separatedBy: ",").map{ CLLocationDegrees($0)! }
return CLLocationCoordinate2D(latitude: latLng[0], longitude: latLng[1])
}
let polyLine = MKPolyline(coordinates: coordinates , count: coordinates.count)
self.busMapView.add(polyLine)
}
}
extension MapVC: CLLocationManagerDelegate {
fileprivate func configureLocationServices() {
if authorizationStatus == .notDetermined {
locationManager.requestAlwaysAuthorization()
} else {
return
}
}
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
guard let latitude = self.currentBusLate else { return }
guard let longitude = self.currentBusLong else { return }
let Location = CLLocation(latitude: latitude, longitude: longitude)
centerMapOnBusLocation(location: Location)
}
}
and this is DisplayAlertMessage:
func DisplayAlertMessage(alertControllerTitle: String , actionButtonTitle: String , alertMessage: String) -> Void{
let alertcontroller = UIAlertController(title: alertControllerTitle, message: alertMessage , preferredStyle: .alert)
let okaction = UIAlertAction(title: actionButtonTitle, style: .default, handler: nil)
alertcontroller.addAction(okaction)
self.present(alertcontroller, animated: true, completion: nil)
}
I can see no code Error i do the #OBJC the selector syntax is right but i still get the Error (unrecognized selector sent to instance) can you help me with that??
In target-action pattern, the target object needs to implement the action method.
But in your code:
self.callFuncTimer = Timer.scheduledTimer(timeInterval: 5, target: self, selector: #selector(self.busInfoObject.downloadBusData(userId:warning:delegate:completed:)), userInfo: nil, repeats: true)
You use self as target, which is an instance of MapVC, that does not implement the method downloadBusData(userId:warning:delegate:completed:).
When you specify #selector(someInstance.methodName(...)) for the action method, you need to pass someInstance to the target object. In your case someInstance is self.busInfoObject.
Which means the line creating a Timer should become like this:
self.callFuncTimer = Timer.scheduledTimer(timeInterval: 5, target: self.busInfoObject, selector: #selector(self.busInfoObject.downloadBusData(userId:warning:delegate:completed:)), userInfo: nil, repeats: true)
But this does not work.
I was stupid enough that I have almost forgotten to tell you another important thing in target-action pattern.
Which is,
The signature of the action method is definitely fixed according to the target.
When using Timer, the signature needs to be the same as this:
class func scheduledTimer(timeInterval: TimeInterval, target: Any, selector: Selector, userInfo: Any?, repeats: Bool) -> Timer
- (void)timerFireMethod:(NSTimer *)timer
The notation is in Objective-C format, but the action method for Timer needs to have one and only one argument of type Timer (it's NSTimer in Objective-C.)
So, you may need to define a method matches the signature in your MapVC:
func timerFired(_ timer: Timer) {
self.busInfoObject.downloadBusData(userId: ...,
warning: {_, _, _ in
...
},
delegate: self,
completed: {
...
})
}
And change the line setting the timer to:
self.callFuncTimer = Timer.scheduledTimer(timeInterval: 5, target: self, selector: #selector(self.timerFired(_:)), userInfo: nil, repeats: true)
Sorry, for showing you an incomplete answer, please try with filling ... in my updated code.
Mistakes you have done here are
You are passing target as self that means your selector should be in MapVC
The selector you are passing is incorrect. According to Apple docs your selector should have signature
-(void)timerFireMethod:(NSTimer *)timer
Refer this https://developer.apple.com/documentation/foundation/timer/1412416-scheduledtimer
So to make it work do this:
func timerFireMethod(_ timer: Timer?) {
self.busInfoObject.downloadBusData(your parameters)
}
// Register timer
self.callFuncTimer = Timer.scheduledTimer(timeInterval: 5, target: self, selector: #selector(self.timerFireMethod), userInfo: nil, repeats: true)
To make it easy you can use this method
self.callFuncTimer = Timer(timeInterval: 5, repeats: true, block: { (timer) in
print("timer")
})
Your settled target for timer is wrong. You should set function for timer and call.
func timerUpdates(_ timer : Timer)
{
self.busInfoObject.downloadBusData(userId:warning:delegate:completed:)
}
self.callFuncTimer = Timer.scheduledTimer(timeInterval: 5, target:#selector(self.timerUpdates:) self, selector: #selector(), userInfo: nil, repeats: true)
Your invokation of the method missing some parameters
func downloadBusData(userId: String,warning: #escaping (String,String,String) -> Void, delegate : MapVC ,completed : #escaping DownloadCompleted)
as per your declaration it should have userId,warning,delegate and the completion handler
When you call function for every 5 seconds replace with this line and Please follow the convention of Swift. Write First letter capital for class name.
self.callFuncTimer = Timer.scheduledTimer(timeInterval: 5, target: self, selector: #selector(BusLocationModel.downloadBusData(userId:warning:delegate:completed:)), userInfo: nil, repeats: true)
Related
I created a class based on this code: https://gist.github.com/etartakovsky/06b8c9894458a3ff1b14
When I try to instantiate the class and call the tick methods, I get "unrecognized selector sent to instance" error. I reviewed the code over and over but don't understand why this is happening, any advice is appreciated:
StopWatch Class source:
import Foundation
import QuartzCore
class StopWatch: NSObject{
private var displayLink: CADisplayLink!
private let formatter = DateFormatter()
var callback: (() -> Void)?
var elapsedTime: CFTimeInterval!
override init() {
super.init()
self.displayLink = CADisplayLink(target: self, selector: "tick:")
displayLink.isPaused = true
displayLink.add(to: RunLoop.main, forMode: RunLoopMode.commonModes)
self.elapsedTime = 0.0
formatter.dateFormat = "mm:ss,SS"
}
convenience init(withCallback callback: #escaping () -> Void) {
self.init()
self.callback = callback
}
deinit {
displayLink.invalidate()
}
func tick(sender: CADisplayLink) {
elapsedTime = elapsedTime + displayLink.duration
callback?()
}
func start() {
displayLink.isPaused = false
}
func stop() {
displayLink.isPaused = true
}
func reset() {
displayLink.isPaused = true
elapsedTime = 0.0
callback?()
}
func elapsedTimeAsString() -> String {
return formatter.string(from: Date(timeIntervalSinceReferenceDate:elapsedTime))
}
}
And here is the ViewController Code:
import UIKit
class ActivityViewController: UIViewController {
let stopwatch = StopWatch()
#IBOutlet weak var elapsedTimeLabel: UILabel!
func tick() {
elapsedTimeLabel.text = stopwatch.elapsedTimeAsString()
}
override func viewDidLoad() {
super.viewDidLoad()
tick()
stopwatch.callback = self.tick
stopwatch.start()
// Do any additional setup after loading the view.
}
}
In Swift 3 use the #selector syntax
self.displayLink = CADisplayLink(target: self, selector: #selector(tick))
In Swift 4 additionally you have to insert #objc at the beginning of the action
#objc func tick(...
Try two things. Use the new (and safer) selector syntax introduced by Swift 3:
CADisplayLink(target: self, selector: #selector(tick(sender:)))
and be sure to expose your tick method to Objective-C (the rules have changed in Swift 4):
#objc func tick(sender: CADisplayLink) {
...
}
To make it clear: unrecognized selector sent to instance is an error in MessagePassing scenario which means the desired selector which is:
func tick(sender: CADisplayLink) {...}
and has to receive the message is unrecognized.
It cannot be found because of the wrong way of addressing to it.
as other members said, you have to change your target selector by adding #selector(tick):
self.displayLink = CADisplayLink(target: self, selector: #selector(tick))
you can find more details about the error in this thread
I have the following Singleton
class SharingManager{
var smallBigText : String = "KLANG!"
static let sharedInstance = SharingManager()
}
I use it to set the text of the following UILabel
#IBOutlet weak var kleinGrossLabel: UILabel!
I initialize its text here, in my ViewController:
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
self.kleinGrossLabel.text = SharingManager.sharedInstance.smallBigText
}
I reset the SharingManager.sharedInstance.smallBigText in an instance method of my SoundEvent class:
class SoundEvent {
var text:String
var duration:Double
init(text: String, duration: Double){
self.text = text
self.duration = duration
}
func startEvent(){
SharingManager.sharedInstance.smallBigText = self.text
}
func getDuration() -> Double{
return self.duration
}
}
When I run the app, the UILabel text remains as "KLANG" and is never changed.
It should be changed when I call startEvent in the following function:
func playEvent(eventIndex : Int){
if (eventIndex < 2){
let currEvent = self.eventArray[eventIndex]
currEvent?.startEvent()
let nextIndex = eventIndex + 1
//NSTimer.scheduledTimerWithTimeInterval(0.4, target: SomeClass.self, selector: Selector("someClassMethod"), userInfo: nil, repeats: true)
NSTimer.scheduledTimerWithTimeInterval((currEvent?.duration)!, target: self, selector: Selector("playEvent:"), userInfo: NSNumber(integer: nextIndex), repeats: false)
}
else if (eventIndex==2){
self.eventArray[eventIndex]?.startEvent()
NSTimer.scheduledTimerWithTimeInterval(0.4, target: self, selector: Selector("sentenceDidFinish"), userInfo: nil, repeats: false)
}
else{
//Do Nothing
}
}
Which I call here in my ViewController
var s1:Sentence = Sentence(type: "S3")
s1.start()
Which in the Sentence class does this:
func start(){
self.playEvent(0)
}
Somehow it breaks this flow of logic, or if the expected sequence of events IS executing, then it follows that I am not actually changing the UILabel's text when I change the shared Singleton resource var smallBigText
For clarity here are all the main .swift
https://gist.github.com/anonymous/07542f638fc5b9a3c4e9
https://gist.github.com/anonymous/10f5f0deb03f9adc354c
https://gist.github.com/anonymous/94fda980836dc057b05b
I have this problem with my application, guess i'm approaching this problem wrong.
For every ten seconds i wish to save my current location to the phone.
And every one minute i wish to upload the current location to a web server.
How do i do this?
This is my code so far.
self.locationManager.requestAlwaysAuthorization()
self.locationManager.requestWhenInUseAuthorization()
if CLLocationManager.locationServicesEnabled() {
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
}
}
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]){
let newLocation = locations.last! as CLLocation
saveLocations.saveLocation(newLocation.coordinate.longitude, latitude: newLocation.coordinate.latitude)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func startButtonClicked(sender: AnyObject) {
if(startButton.titleLabel?.text == "STARTA") {
tenSecTimer = NSTimer.scheduledTimerWithTimeInterval(10, target:self, selector: Selector("tenSecTimer:"), userInfo: nil, repeats: true)
sixtySecTimer = NSTimer.scheduledTimerWithTimeInterval(60, target: self, selector: Selector("sixtySecondsTimer:"), userInfo: nil, repeats: true)
locationManager.startUpdatingLocation()
startButton.setTitle("STOPPA", forState: UIControlState.Normal)
startButton.backgroundColor = UIColor.redColor()
} else {
tenSecTimer.invalidate()
sixtySecTimer.invalidate()
locationManager.stopUpdatingLocation()
startButton.setTitle("STARTA", forState: UIControlState.Normal)
startButton.backgroundColor = UIColor.greenColor()
}
}
//One minute upload
func sixtySecondsTimer(timer : NSTimer){
print("60SEK")
}
//10Second timer
func tenSecTimer(timer : NSTimer) {
print("10SEK")
}
In order to get the automatic alert asking the user for permission to use their location, then you will need to add the NSLocationAlwaysUsageDescription and/or NSLocationWhenInUseUsageDescription keys to your Info.plist, of course depending on which location update type you will make use of. It could look like the following:
<key>NSLocationWhenInUseUsageDescription</key>
<string>We need to know your location because it makes it easier to stalk you</string>
<key>NSLocationAlwaysUsageDescription</key>
<string>We need to know your location because it makes it easier to stalk you</string>
===
UPDATE 1
OP provided more details about the problem
===
To make a POST request then you can either make use of NSURLSession or some popular third party networking library. Many people makes use of either AFNetworking (Obj-C) or Alamofire (Swift) to make their lives easier and code prettier. You can install both of these through CocoaPods.
The following example will be based on Alamofire, but the code will be very similar for AFNetworking. NSURLSession is a different case, which sometimes is needed in complex multi-threading apps or whenever you wanna provide better background support.
NOTE: This code is simply a demo and is all contained in the same UIViewController which is bad practice. You will need to refactor it yourself into seperate models for better code. Also, the NSDateFormatter in the NSDate extension is highly inefficient. Also, this test makes use of HTTP, so ATS is turned off.
//
// ViewController.swift
// LocationUpload
//
// Created by Stefan Veis Pennerup on 13/11/15.
// Copyright © 2015 Kumuluzz. All rights reserved.
//
import UIKit
import CoreLocation
import Alamofire
class ViewController: UIViewController, CLLocationManagerDelegate {
// MARK: - Properties
private let locationManager = CLLocationManager()
private var tenSecTimer = NSTimer()
private var sixtySecTimer = NSTimer()
private var savedLocations = [CLLocation]()
// MARK: - Storyboard outlets
#IBOutlet weak var startButton: UIButton!
// MARK: - Lifecycle methods
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
requestLocationAuthorization()
}
// MARK: - Storyboard actions
#IBAction func startButtonClicked(sender: UIButton) {
let buttonText = startButton.titleLabel?.text!
let shouldTurnUpdatesOn = buttonText == "STARTA"
toggleLocationUpdates(shouldTurnUpdatesOn)
}
// MARK: - Authorization
private func requestLocationAuthorization() {
self.locationManager.requestAlwaysAuthorization()
if CLLocationManager.locationServicesEnabled() {
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
}
}
// MARK: - Helper methods
private func toggleLocationUpdates(toggle: Bool) {
if toggle {
tenSecTimer = NSTimer.scheduledTimerWithTimeInterval(10,
target:self,
selector: Selector("tenSecTimer:"),
userInfo: nil, repeats: true)
sixtySecTimer = NSTimer.scheduledTimerWithTimeInterval(60,
target: self,
selector: Selector("sixtySecondsTimer:"),
userInfo: nil, repeats: true)
locationManager.startUpdatingLocation()
startButton.setTitle("STOPPA", forState: UIControlState.Normal)
startButton.backgroundColor = UIColor.redColor()
}
else {
tenSecTimer.invalidate()
sixtySecTimer.invalidate()
locationManager.stopUpdatingLocation()
startButton.setTitle("STARTA", forState: UIControlState.Normal)
startButton.backgroundColor = UIColor.greenColor()
}
}
// MARK: - CLLocationManagerDelegate
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
print("\(self.dynamicType), locationManager:didUpdateLocations")
savedLocations += locations
}
// MARK: - Callback methods
func sixtySecondsTimer(timer : NSTimer) {
print("\(self.dynamicType), sixtySecondsTimer")
uploadLocationsToBackend(savedLocations)
}
func tenSecTimer(timer : NSTimer) {
print("\(self.dynamicType), tenSecTimer")
}
// MARK: - Parsers
private struct JsonConstants {
static let Locations = "locations"
static let Timestamp = "timestamp"
static let Latitude = "latitude"
static let Longitude = "longitude"
}
private func parseLocationsToDictionary(locations: [CLLocation]) -> [String: AnyObject] {
var locationsDictionaries = [[String: AnyObject]]()
for loc in locations {
let locDict: [String: AnyObject] = [
JsonConstants.Timestamp: loc.timestamp.toISO8601(),
JsonConstants.Latitude: loc.coordinate.latitude,
JsonConstants.Longitude: loc.coordinate.longitude
]
locationsDictionaries += [locDict]
}
return [JsonConstants.Locations: locationsDictionaries]
}
// MARK: - Network
private func uploadLocationsToBackend(locations: [CLLocation]) {
let url = "http://httpbin.org/post"
let params = parseLocationsToDictionary(locations)
Alamofire.request(.POST, url, parameters: params, encoding: .JSON, headers: nil)
.responseJSON { response in
print("\(self.dynamicType) response: \(response)")
}
}
}
extension NSDate {
func toISO8601() -> String {
let iso8106Formatter = NSDateFormatter()
iso8106Formatter.timeZone = NSTimeZone(name: "UTC")
iso8106Formatter.locale = NSLocale(localeIdentifier: "en_US_POSIX")
iso8106Formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"
return iso8106Formatter.stringFromDate(self)
}
}
I created a timer app that runs on the iphone.
I wish we could control it iPhone and Watch
The controls (Play, Stop, Restart) with the iPhone works fine, my meter is displayed on the Watch.
Watch on the Stop works well, for against the Start does not work, the meter does not turn on iPhone or the Watch.
Restart the works too.
My Label on the iPhone is very slow to change if the information comes from the Watch, but works well in the other direction, toward the iPhone Watch
Have you noticed this problem, it is a problem related to WatchConnectivity
Thanks for your help
Below is my code:
ViewController.swift
import UIKit
import WatchConnectivity
class ViewController: UIViewController, WCSessionDelegate {
#IBOutlet weak var timerLabel: UILabel!
#IBOutlet weak var watchLabel: UILabel!
var session: WCSession!
var timerCount = 0
var timerRunning = false
var timer = NSTimer()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
if (WCSession.isSupported()) {
let session = WCSession.defaultSession()
session.delegate = self
session.activateSession()
if session.paired != true {
print("Apple Watch is not paired")
}
if session.watchAppInstalled != true {
print("WatchKit app is not installed")
}
} else {
print("WatchConnectivity is not supported on this device")
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func startButton(sender: UIButton) {
startPlay()
}
#IBAction func stopButton(sender: UIButton) {
stopPlay()
}
#IBAction func restartButton(sender: UIButton) {
restartPlay()
}
//Receive messages from watch
func session(session: WCSession, didReceiveMessage message: [String : AnyObject], replyHandler: ([String : AnyObject]) -> Void) {
var replyValues = Dictionary<String, AnyObject>()
//let viewController = self.window!.rootViewController as! ViewController
switch message["command"] as! String {
case "start" :
startPlay()
replyValues["status"] = "Playing"
case "stop" :
stopPlay()
replyValues["status"] = "Stopped"
case "restart" :
restartPlay()
replyValues["status"] = "Stopped"
default:
break
}
replyHandler(replyValues)
}
//Counter Timer
func counting(timer:NSTimer) {
self.timerCount++
self.timerLabel.text = String(timerCount)
let requestValues = ["timer" : String(timerCount)]
let session = WCSession.defaultSession()
session.sendMessage(requestValues, replyHandler: nil, errorHandler: { error in print("error: \(error)")})
}
//Fonction Play
func startPlay() {
if timerRunning == false {
self.timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: Selector("counting:"), userInfo: nil, repeats: true)
self.timerRunning = true
self.watchLabel.text = "START"
}
}
//Fonction Stop
func stopPlay() {
if timerRunning == true {
self.timer.invalidate()
self.timerRunning = false
self.watchLabel.text = "STOP"
}
}
//Fonction Restart
func restartPlay() {
self.timerCount = 0
self.timerLabel.text = "0";
let requestValues = ["timer" : "0"]
let session = WCSession.defaultSession()
session.sendMessage(requestValues, replyHandler: nil, errorHandler: { error in print("error: \(error)")})
}
}
InterfaceController.swift
import WatchKit
import Foundation
import WatchConnectivity
class InterfaceController: WKInterfaceController, WCSessionDelegate {
#IBOutlet var watchLabel: WKInterfaceLabel!
#IBOutlet var statusLabel: WKInterfaceLabel!
//Receiving message from iphone
func session(session: WCSession, didReceiveMessage message: [String : AnyObject]) {
self.watchLabel.setText(message["timer"]! as? String)
// self.statusLabel.setText(message["command"]! as? String)
}
override func awakeWithContext(context: AnyObject?) {
super.awakeWithContext(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()
if (WCSession.isSupported()) {
let session = WCSession.defaultSession()
session.delegate = self
session.activateSession()
}
}
override func didDeactivate() {
// This method is called when watch view controller is no longer visible
super.didDeactivate()
}
#IBAction func startButtonWatch() {
if WCSession.defaultSession().reachable == true {
let requestValues = ["command" : "start"]
let session = WCSession.defaultSession()
session.sendMessage(requestValues, replyHandler: { reply in
self.statusLabel.setText(reply["status"] as? String)
}, errorHandler: { error in
print("error: \(error)")
})
}
}
#IBAction func stopButtonWatch() {
if WCSession.defaultSession().reachable == true {
let requestValues = ["command" : "stop"]
let session = WCSession.defaultSession()
session.sendMessage(requestValues, replyHandler: { reply in
self.statusLabel.setText(reply["status"] as? String)
}, errorHandler: { error in
print("error: \(error)")
})
}
}
#IBAction func restartButtonWatch() {
if WCSession.defaultSession().reachable == true {
let requestValues = ["command" : "restart"]
let session = WCSession.defaultSession()
session.sendMessage(requestValues, replyHandler: { reply in
self.statusLabel.setText(reply["status"] as? String)
}, errorHandler: { error in
print("error: \(error)")
})
}
}
}
You should use this:
func startPlay() {
if timerRunning == false {
//self.timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: Selector("counting:"), userInfo: nil, repeats: true)
self.timer = NSTimer(timeInterval: 1, target: self, selector: "counting:", userInfo: nil, repeats: true)
NSRunLoop.mainRunLoop().addTimer(self.timer, forMode: NSRunLoopCommonModes)
self.timerRunning = true
self.watchLabel.text = "Start"
}
}
I cant explain you why we need use NSRunLoop explicitly. I stuck with same timer issue when develop an app with data transfer. Some answers you can find in google by query "nstimer run loop" or here.
And i pref use this for restart:
func restartPlay() {
self.timerCount = 0
self.timerLabel.text = "0";
stopPlay()
startPlay()
self.watchLabel.text = "Restarted"
}
Cheers.
This functional and optimized code :
//Fonction Play
func startPlay() {
if timerRunning == false {
self.mytimer = NSTimer(timeInterval: 1, target: self, selector: "counting:", userInfo: nil, repeats: true)
NSRunLoop.mainRunLoop().addTimer(self.mytimer, forMode: NSRunLoopCommonModes)
timerRunning = true
dispatch_async(dispatch_get_main_queue()) {
self.watchLabel.text = "PLAYING"
}
}
}
I have 8 fake users, including me, with different locations on Parse. If user presses on an annotation on my map, I'd like to get an array with their user.username to open a direct chat with the choosen one among them sending the user.username to my next NewChatVC receiver var via prepareForSegue. In order to achieve this, I'm try'n to create an array closeUsersArray with first, for say, ten people selected among closer ones. Distance filter in km seems to be fine, but when I try to fill my array, in the console I get many repetitions instead only 8 names with:
self.closeUsersArray.append(user.username!) //MARK: Test
or a group/array or filled with repetitions of those 8 names this happens with:
println("this is the array of users * \(self.closeUsersArray) *") //MARK: Test
update
I discovered that in locationManager code with println("evaluates") evaluates multiple times, calling displayLocationInfo with calls createAnnotations multiple times. I think that I should try to clear conditions, maybe are too many
below my file, thanks in advance
import UIKit
import MapKit
import CoreLocation
import Parse
class MapViewController: UIViewController, MKMapViewDelegate, CLLocationManagerDelegate, UIActionSheetDelegate {
#IBOutlet weak var mapView: MKMapView!
#IBOutlet weak var segmentedControl: UISegmentedControl!
let locationManager = CLLocationManager()
let kDefaultKmForUserOnMap = 50.0 //default value
var limitNumberForQueryResults = 10
var closeUsersArray:[String] = [] //MARK: Test
let defaults = NSUserDefaults.standardUserDefaults()
var withinKms : Double!
override func viewDidLoad()
{
super.viewDidLoad()
//MARK: checks if the variable is nil, if yes, attributes a value
if defaults.doubleForKey("withinKms") <= 0 {
defaults.setDouble(kDefaultKmForUserOnMap, forKey: "withinKms")
defaults.synchronize()
withinKms = defaults.doubleForKey("withinKms")
println("MapViewController - viewDidLoad - var kKmRadiusForUsersOnMap was never set before, so now is set to \(withinKms) ")
} else {
withinKms = defaults.doubleForKey("withinKms")
println("MapViewController - viewDidLoad - else occurred and var test is \(withinKms)")
}
self.locationManager.delegate = self
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest
self.locationManager.requestWhenInUseAuthorization()
self.locationManager.startUpdatingLocation()
self.mapView.showsUserLocation = true
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
}
override func viewDidAppear(animated: Bool)
{
super.viewDidAppear(animated)
self.segmentedControl.selectedSegmentIndex = 0
withinKms = self.defaults.doubleForKey("withinKms")
println("MapViewController - viewDidAppear - radius shown on map is * \(withinKms) * ")
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "fromMapToNewChats" {
//MARK: Hint - this is the standard way to pass data to a NOT embedded VC
var nextVC : NewChatsFromHomeVC = segue.destinationViewController as! NewChatsFromHomeVC
nextVC.calledFromVC = "MapViewController"
nextVC.receivedReceiver = "Specific User"
// // nextVC.filterToParse = self.channleKeywordReceived
}
}
//************************************************
//MARK: send message by touching an annotation
func mapView(mapView: MKMapView!, didSelectAnnotationView view: MKAnnotationView!) {
println("anotation pressed: \(view.annotation.title)")
self.performSegueWithIdentifier("fromMapToNewChats", sender: self)
}
//************************************************
// MARK: - Location Delegate Methods
func locationManager(manager: CLLocationManager!, didUpdateLocations locations: [AnyObject]!)
{
let point = PFGeoPoint(latitude:manager.location.coordinate.latitude, longitude:manager.location.coordinate.longitude)
let location = locations.last as! CLLocation
let center = CLLocationCoordinate2D(latitude: location.coordinate.latitude, longitude: location.coordinate.longitude)
let region = MKCoordinateRegion(center: center, span: MKCoordinateSpan(latitudeDelta: 1, longitudeDelta: 1))
self.mapView.setRegion(region, animated: true)
CLGeocoder().reverseGeocodeLocation(manager.location, completionHandler: {(placemarks, error)->Void in
if (error != nil)
{
println("Error: " + error.localizedDescription)
return
}
if placemarks.count > 0
{
let pm = placemarks[0] as! CLPlacemark
self.displayLocationInfo(pm, point: point)
println("evaluates 3")
}
else
{
println("Error with the data.")
}
})
}
func displayLocationInfo(placemark: CLPlacemark, point: PFGeoPoint)
{
self.locationManager.stopUpdatingLocation()
self.createAnnotations(point, address: "\(placemark.locality) \(placemark.administrativeArea) \(placemark.postalCode) \(placemark.country)")
}
func locationManager(manager: CLLocationManager!, didFailWithError error: NSError!)
{
println("Error: " + error.localizedDescription)
}
// timelineMessageDataArray.removeAll(keepCapacity: true) //erase previus contents
// println("timelineData cleared")
// MARK: - Create Annotation
func createAnnotations(point: PFGeoPoint, address: String)
{
var query = PFUser.query()
query?.whereKey("location", nearGeoPoint: point, withinKilometers: withinKms)
query?.orderByAscending("location") //MARK: Put list in order
query?.limit = self.limitNumberForQueryResults
query?.findObjectsInBackgroundWithBlock({ (objects, error) -> Void in
if error == nil
{
for(var i = 0; i < objects!.count; i++)
{
let user = objects![i] as! PFUser
var myHomePin = MKPointAnnotation()
let userPoint = user["location"] as! PFGeoPoint
myHomePin.coordinate = CLLocationCoordinate2DMake(userPoint.latitude, userPoint.longitude)
myHomePin.title = user.username
myHomePin.subtitle = address
self.mapView.addAnnotation(myHomePin)
// self.closeUsersArray.append(user.username!) //MARK: Test
}
// println("this is the array of users * \(self.closeUsersArray) *") //MARK: Test
}
else
{
println("Error: " + error!.localizedDescription)
}
})
}
// MARK: - Action Delegate
func actionSheet(actionSheet: UIActionSheet, clickedButtonAtIndex buttonIndex: Int)
{
switch(buttonIndex)
{
case 0: //Destructive button
break
case 1: // 25.0 Km
// NSUserDefaults.standardUserDefaults().setDouble(25.0, forKey: "withinKms")
self.defaults.setDouble(50.0, forKey: "withinKms")
self.defaults.synchronize()
self.locationManager.startUpdatingLocation()
break;
case 2: // 50.0 Km
self.defaults.setDouble(100.0, forKey: "withinKms")
self.defaults.synchronize()
self.locationManager.startUpdatingLocation()
break;
case 3: // 100.0 Km
self.defaults.setDouble(200.0, forKey: "withinKms")
self.defaults.synchronize()
self.locationManager.startUpdatingLocation()
break;
case 4: // 200.0 Km
self.defaults.setDouble(300.0, forKey: "withinKms")
self.defaults.synchronize()
self.locationManager.startUpdatingLocation()
break;
default:
break;
}
}
// MARK: - Actions
#IBAction func homeAction(sender: AnyObject)
{
self.dismissViewControllerAnimated(true, completion: nil)
}
#IBAction func indexChanged(sender: UISegmentedControl)
{
switch segmentedControl.selectedSegmentIndex
{
case 0:
println("map clicked") //MARK: this one never evaluates!
case 1:
self.performSegueWithIdentifier("showListView", sender: self)
println("showListView clicked")
default:
break;
}
}
#IBAction func radiusAction(sender: UIButton)
{
// UIActionSheet(title: nil, delegate: self, cancelButtonTitle: "cancel", destructiveButtonTitle: nil, otherButtonTitles: "25.0 Miles", "50.0 Miles", "100.0 Miles", "200.0 Miles").showInView(self.view)
UIActionSheet(title: nil, delegate: self, cancelButtonTitle: "cancel", destructiveButtonTitle: nil, otherButtonTitles: "50 Km", "100 Km", "200 Km", "300 Km").showInView(self.view)
}
#IBAction func profileButton(sender: UIBarButtonItem) {
// let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("ProfileNavControllerID") as? UIViewController
//
// self.presentViewController(vc!, animated: true, completion: nil)
let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("ProfileNavControllerID") as! UIViewController
self.presentViewController(vc, animated: true, completion: nil)
}
}
Not sure if this would solve the problem but maybe changing your for loop might help. Try this:
for nearbyUser in objects {
let user = nearbyUser.objectForKey("username")
var myHomePin = MKPointAnnotation()
let userPoint = nearbyUser.objectForKey("location") as! PFGeoPoint
myHomePin.coordinate = CLLocationCoordinate2DMake(userPoint.latitude, userPoint.longitude)
myHomePin.title = ("\(user)")
myHomePin.subtitle = address
self.mapView.addAnnotation(myHomePin)
self.closeUsersArray.append(user) //MARK: Test
}
Try something like that maybe