Reading CMSensorDataList in Swift - ios

I am making an apple watch app where it gets the accelerometer data from a date to another date. By using this code:
#IBAction func start() {
WKInterfaceDevice.currentDevice().playHaptic(WKHapticType.Start)
let startDate = NSDate()
NSUserDefaults.standardUserDefaults().setObject(startDate, forKey: "StartDate")
}
#IBAction func stop() {
WKInterfaceDevice.currentDevice().playHaptic(WKHapticType.Stop)
let endDate = NSDate()
let startDate = NSUserDefaults.standardUserDefaults().objectForKey("StartDate") as! NSDate
printData(startDate, endDate: endDate)
}
Then when I got to export the data using the function printData() which this is the code for that function
func printData(startDate: NSDate, endDate: NSDate) {
let recorder = CMSensorRecorder()
let list: CMSensorDataList = recorder.accelerometerDataFromDate(startDate, toDate: endDate)!
for (index, data) in list.enumerate() {
print(index, data)
}
}
For the enumeration I have an extension and here is the code for the extension:
extension CMSensorDataList: SequenceType {
public func generate() -> NSFastGenerator {
return NSFastGenerator(self)
}
}
When I press the stop button the app crashes. I got all of my code from this question(Swift watchOS 2 - CMSensorDataList) but it doesn't work on my device. Does anyone know what is happening?

You need an entry for NSMotionUsageDescription in your Info.plist file. If you crash while attached to Xcode, it should give you this information.

Related

How to load JSON Data into FSCalendar Swift 5

I am trying to load my JSON Data into FSCalendar.
I understand from the documentation that when you use an array that can be show in the calendar with the dots. I am wanting to do that same thing but instead to have the dots appear from my dates listed in my JSON file. I have loaded the JSON file into my bundle as well as made a function to load the JSON data and I have created a structure.
My problem comes in when I am trying to use an array from the structure and then load that array into the "func calendar(_ calendar: FSCalendar, numberOfEventsFor date: Date) -> Int {" function. I have been working on this for a while and could use some help. Thanks
JSON Data: - I titled the JSON file data.json
[
{
"event": "Christmas",
"date": [
"2021-01-02",
"2021-01-03",
"2021-01-04"
]
}
]
View Controller:
import UIKit
import FSCalendar
class ViewController: UIViewController, FSCalendarDelegate, FSCalendarDataSource, FSCalendarDelegateAppearance {
//MARK: - calendar variables
#IBOutlet var calendar: FSCalendar!
#IBOutlet weak var dateLabel: UILabel!
fileprivate let gregorian: Calendar = Calendar(identifier: .gregorian)
fileprivate lazy var dateFormatter1: DateFormatter = {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy/MM/dd"
return formatter
}()
fileprivate lazy var dateFormatter2: DateFormatter = {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd"
return formatter
}()
// These are just test data to see the dots on the calendar
var datesWithEvents = ["2021-01-15","2021-01-16","2021-01-17","2021-01-18"]
var datesWithMultipleEvents = ["2021-01-20","2021-01-21","2021-01-22","2021-01-23"]
//MARK: - JSON variable
// var result: Results?
let data = DataLoader().eventData
//MARK: - viewdidload
override func viewDidLoad() {
super.viewDidLoad()
// parseJSON()
let data = DataLoader().eventData
print(data)
calendar.delegate = self
calendar.dataSource = self
}
deinit {
print("\(#function)")
}
//MARK: - calendar functions
func calendar(_ calendar: FSCalendar, didSelect date: Date, at monthPosition: FSCalendarMonthPosition) {
let formatter = DateFormatter()
formatter.dateFormat = "EEEE MM-dd-YYYY"
let string = formatter.string(from: date)
print("\(string)")
}
func calendar(_ calendar: FSCalendar, appearance: FSCalendarAppearance, eventDefaultColorsFor date: Date) -> [UIColor]? {
let key = self.dateFormatter2.string(from: date)
// this is the test with the hard coded arrays, I think i need to
implement the JSON here but am not sure how
if self.datesWithMultipleEvents.contains(key) {
return [UIColor.magenta, appearance.eventDefaultColor, UIColor.red]
}
return nil
}
func calendar(_ calendar: FSCalendar, numberOfEventsFor date: Date) -> Int {
let dateString = self.dateFormatter2.string(from: date)
if self.datesWithEvents.contains(dateString) {
return 1
}
if self.datesWithMultipleEvents.contains(dateString) {
return 3
}
return 0
}
Data Model:
struct EventsData: Codable {
var event: String
var date: [String]
}
JSON loader:
import Foundation
public class DataLoader {
#Published var eventData = [EventsData]()
init() {
load()
}
func load() {
if let fileLocation = Bundle.main.url(forResource: "data", withExtension: "json") {
//do catch incase of error
do {
let data = try Data(contentsOf: fileLocation)
let jsonDecoder = JSONDecoder()
let dataFromJson = try jsonDecoder.decode([EventsData].self, from: data)
self.eventData = dataFromJson
} catch {
print(error)
}
}
}
}
EDIT:
func calendar(_ calendar: FSCalendar, numberOfEventsFor date: Date) -> Int {
let dateString = self.dateFormatter2.string(from: date)
if self.eventData.contains(where: //<#T##(EventsData) throws -> Bool#>) //How to get this to do the contains like in the next if statement
if self.datesWithEvents.contains(dateString) {
return 1
}
if self.datesWithMultipleEvents.contains(dateString) {
return 3
}
return 0
}
Check if you have this in your code:
// FSCalendarDataSource
func calendar(calendar: FSCalendar!, hasEventForDate date: NSDate!) -> Bool {
return shouldShowEventDot
}
Just convert your string date into Date with DateFormatter and then just go through your array and check if the date (from the calendar function) matches any of your dates in the event.
You have a number of problems.
First, you are trying to use SwiftUI constructs (#Published) in a Swift app. That won't work.
Second, you have your dates coming in as an array of strings. I suggest setting up your Codable protocol conformance using a custom Date decoding stategy.
This link shows how to set up your JSONDecoder to use a custom date decoding strategy that takes a custom DateFormatter.
Next, you need some way to update your UI when new data is read from your JSON input. SwiftUI has built-in mechanisms for automatically responding to state changes, but you're not using SwiftUI, so those don't apply.
You have a DataLoader class that loads JSON data. It currently reads hard-coded JSON from a file, synchronously. I assume that you want to load the data from the internet, which would be async. Thus your approach won't work.
I suggest you set up your DataLoader class to either have a delegate that it notifies when it has completed loading new data, or set it up to take requests with a completion handler.
If you don't know how to do either of those things then you need to stop and learn some fundamentals. Read up on Swift async networking, completion handlers, and the delegate design pattern before you continue.

How to use NSUserDefaults to check between current time and last posting time?

My idea is that when Button "A" is tapped every time, it will set the NSDate value automatically. When the current time is larger than the existing NSDate value, it print "yes". Here is my code but I dont know what's wrong.
import UIKit
class ViewController: UIViewController {
var currentDateTime = NSDate()
override func viewDidLoad() {
super.viewDidLoad()
observeTime()
}
#IBAction func show(_ sender: Any) {
print(UserDefaults.standard.dictionaryRepresentation())
}
let userDefaults = UserDefaults.standard
func observeTime() {
let posttime = userDefaults.object(forKey: "LastPostingTime") as? NSDate
if ((posttime?.isGreaterThanDate(dateToCompare: currentDateTime))!) {
print("yes")
}
}
#IBAction func hihi(_ sender: Any) {
observeTime()
userDefaults.set(NSDate(), forKey: "LastPostingTime")
}
}
extension NSDate {
func isGreaterThanDate(dateToCompare: NSDate) -> Bool {
//Declare Variables
var isGreater = false
//Compare Values
if self.compare(dateToCompare as Date) == ComparisonResult.orderedDescending {
isGreater = true
}
//Return Result
return isGreater
}
}
In function hihi you first store the current date into NSUserDefaults before reading it. So you will get back what you just stored: the current time.
You may want to read it first, compare it to currentDateTime and then store it into user defaults.

SKStore​Review​Controller, How to use it in a correct way?

I have seen some answer but not satisfied with them and got some idea, but don't know how to use it properly, so that it will execute in proper way, though i think it should be used in App delegates didFinishLaunching, but i wanted to be sure before implement it in Live app without any hustle.
SKStore​Review​Controller is only work for ios 10.3 what i read, could anybody explain with little bit of code in swift and objective c.
UPDATE:
Actually I'm confused about calling the method request​Review(), Where do i need to call this method? in rootViewController's viewDidLoad or in appDelegate's didFinishlaunching ?
Thanks.
SKStoreReviewController is available in iOS 10.3 and later.
According to APPLE's Documents:
You can ask users to rate or review your app while they're using it,
without sending them to the App Store.You determine the points in the
user experience at which it makes sense to call the API and the system
takes care of the rest.
Inorder to display Rate/Review inside the app, you have to add StoreKitframework.
Please find the Sample code for both language:
Objective C:
#import <StoreKit/StoreKit.h>
- (void)DisplayReviewController {
if([SKStoreReviewController class]){
[SKStoreReviewController requestReview] ;
}
}
since xCode 9 you can do:
#import <StoreKit/StoreKit.h>
- (void)DisplayReviewController {
if (#available(iOS 10.3, *)) {
[SKStoreReviewController requestReview];
}
}
Swift:
import StoreKit
func DisplayReviewController {
if #available( iOS 10.3,*){
SKStoreReviewController.requestReview()
}
}
Update: Ask for a rating only after the user has demonstrated engagement with your app
For Swift,
import StoreKit
Add below code to request when you want to ask.
if #available(iOS 10.3, *) {
SKStoreReviewController.requestReview()
}
For Objective C,
1-) Added StoreKit framework from Link Binary With Library
2-) Added framework
#import <StoreKit/StoreKit.h>
3-) Added below code where you want to call App-Review pop-up. In this case, i added in viewDidLoad.
- (void)viewDidLoad {
[super viewDidLoad];
[SKStoreReviewController requestReview];
}
4-) You should be aware of below explain from Apple, When you test in debug mode
When you call this method while your app is still in development mode, a rating/review request view is always displayed so that you can test the user interface and experience. However, this method has no effect when you call it in an app that you distribute using TestFlight.
I think directly calling the below is not an good idea
SKStoreReviewController.requestReview()
It can be done like whenever user opens your app the multiple of 10(10,20,30,...100) then you can show for review
so first of all you need to create a file that will be responsible for everything like saving your application open count in user defaults , retrieving application open count, and showing requestReview()
kindly have a look at the following code snippet
import Foundation
import StoreKit
class SpsRateManager {
private static let instance = SpsRateManager()
var shareinstance: SpsRateManager{
return .instance
}
static func incrementAppOpenedCount() { // called from appdelegate didfinishLaunchingWithOptions:
let userdefault = UserDefaults.standard
let savedvalue = userdefault.integer(forKey: Configuration.APPLICATIONOPENCOUNTSTATUS)
if savedvalue == 0 {
print("Not saved ")
userdefault.set(1, forKey: Configuration.APPLICATIONOPENCOUNTSTATUS)
}
else{
userdefault.set(savedvalue+1, forKey: Configuration.APPLICATIONOPENCOUNTSTATUS)
}
}
static func checkAppopencountandProvideReview(){
let userdefault = UserDefaults.standard
let appopencountvalue = userdefault.integer(forKey: Configuration.APPLICATIONOPENCOUNTSTATUS)
if appopencountvalue % 10 == 0 {
print("its been 10 times so ask for review ")
SpsRateManager().requestReview()
}
else{
print("not enough open count dont show ")
}
}
fileprivate func requestReview() {
if #available(iOS 10.3, *) {
SKStoreReviewController.requestReview()
} else {
// Fallback on earlier versions
// Try any other 3rd party or manual method here.
}
}
}
Adding onto korat's great answer above...
If your supporting a legacy Objective-C app and you want to call DisplayReviewController after a few app opens then do the following:
In your class AppDelegate.m add this:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
int count = [[NSUserDefaults standardUserDefaults] integerForKey:#"LaunchCount"];
if(count < 0) count = 0;
[[NSUserDefaults standardUserDefaults] setInteger:count+1 forKey:#"LaunchCount"];
}
//The application was in background and become active
- (void)applicationWillEnterForeground:(UIApplication *)application {
int count = [[NSUserDefaults standardUserDefaults] integerForKey:#"LaunchCount"];
if(count < 0) count = 0;
[[NSUserDefaults standardUserDefaults] setInteger:count+1 forKey:#"LaunchCount"];
}
and in the controller you want to call the function:
- (void)applicationDidBecomeActive {
if ([[NSUserDefaults standardUserDefaults] integerForKey:#"LaunchCount"] == 5) {
[self DisplayReviewController];
}
}
I think you may implement a method to count when they run the app and store it in UserDefaults, then call requestReview() if the count number is 5 or 10 or something like that (it depends on you), by this way you have more chance of getting a good review.
Here's a utility function I am developing for my own use case that might help a lot of other people. (Feel free to roast and improve/correct my code :D). I am working on a speech practice app and I want to ask for a rating after the user has done a few recordings. I will add the main function and then other helper functions used below it. The brief logic is, you can request a review 3 times a year, so if 1 year has passed, I reset the ask count to 0. Also, the review request won't be presented for each ask. So I have an upper limit of 30 asks before I don't allow the app to attempt review requests anymore. This won't be taken into consideration if the app version has changed, as you can again ask for a review for the new app version.
/// Requests review from user based on certain conditions.
/// 1. Should have recorderd at least 3 recordings (if you want to force attept a review ask don't pass any parameter)
/// 2. Has not already asked for a review today
/// 3. A probabitly of 50% if will ask today
/// 4. If review has not been asked more than 30 times in the same year for the current version
/// - Parameter numberOfRecordings: If the number of recordings is greater than 3 then a review will be asked.
func askForReview(numberOfRecordings: Int = 5) {
let defaults = UserDefaults.standard
let lastAskedReviewAt = defaults.double(forKey: lastAskedReviewAtKey)
let dateStringForLastReviewAsk = getDateString(from: lastAskedReviewAt)
let dateForLastReviewAsk = getDate(from: dateStringForLastReviewAsk) ?? Date(timeIntervalSince1970: 0)
let askedReviewToday = Calendar.current.isDateInToday(dateForLastReviewAsk)
var appReviewRequestsCount = defaults.integer(forKey: appReviewRequestsCountKey)
if Date().localDate().years(from: dateForLastReviewAsk) >= 1 {
defaults.setValue(0, forKey: appReviewRequestsCountKey)
appReviewRequestsCount = 0
}
var isAskingReviewForSameVersion = false
if let currentlyInstalledVersion = getInstalledVersionNumber(), let lastReviewAskedForVersion = defaults.string(forKey: lastReviewAskedForVersionKey) {
if currentlyInstalledVersion == lastReviewAskedForVersion {
isAskingReviewForSameVersion = true
} else {
appReviewRequestsCount = 0
defaults.setValue(0, forKey: appReviewRequestsCountKey)
}
}
let askingReviewTooManyTimes = appReviewRequestsCount >= 30 && isAskingReviewForSameVersion
let totalRecordingsTillDateCount = defaults.integer(forKey: totalRecordingsTillDateCountKey)
let localNumberOfRecordings = max(numberOfRecordings, totalRecordingsTillDateCount)
if localNumberOfRecordings > 3 && Bool.random() && !askedReviewToday && !askingReviewTooManyTimes {
SKStoreReviewController.requestReview()
defaults.setValue(Date().timeIntervalSince1970, forKey: lastAskedReviewAtKey)
if let versionNumber = getInstalledVersionNumber() {
defaults.setValue(versionNumber, forKey: lastReviewAskedForVersionKey)
}
defaults.setValue(appReviewRequestsCount + 1, forKey: appReviewRequestsCountKey)
}
}
Dictionary Keys:
let lastAskedReviewAtKey = "LastAskedReviewAt"
let appReviewRequestsCountKey = "AppReviewRequestsCount"
let lastReviewAskedForVersionKey = "AskedReviewForVersion"
let appVersionNumberKey = "CFBundleShortVersionString"
Helper Functions:
/// Get a string representation in current local time for a timestamp
/// - Parameter timestamp: Timestamp to be converted to date string
/// - Returns: A date string from passed timestamp in dd MMM yyy format
func getDateString(from timestamp: Double) -> String {
let dateFormatter = getDateFormatter()
let date = Date(timeIntervalSince1970: timestamp)
let dateString = dateFormatter.string(from: date)
return dateString
}
/// Get a date from a string of date format dd MMM yyyy.
/// - Parameter dateString: Date string formated as dd MMM yyyy
/// - Returns: A date object by parsing date in dd MMM yyy format
func getDate(from dateString: String) -> Date? {
// print("Date String: ", dateString)
let dateFormatter = getDateFormatter()
return dateFormatter.date(from: dateString) ?? nil
}
//Ref: https://stackoverflow.com/questions/27182023/getting-the-difference-between-two-dates-months-days-hours-minutes-seconds-in
extension Date {
/// Returns the amount of years from another date
func years(from date: Date) -> Int {
return Calendar.current.dateComponents([.year], from: date, to: self).year ?? 0
}
/// Returns the amount of months from another date
func months(from date: Date) -> Int {
return Calendar.current.dateComponents([.month], from: date, to: self).month ?? 0
}
/// Returns the amount of weeks from another date
func weeks(from date: Date) -> Int {
return Calendar.current.dateComponents([.weekOfMonth], from: date, to: self).weekOfMonth ?? 0
}
/// Returns the amount of days from another date
func days(from date: Date) -> Int {
return Calendar.current.dateComponents([.day], from: date, to: self).day ?? 0
}
/// Returns the amount of hours from another date
func hours(from date: Date) -> Int {
return Calendar.current.dateComponents([.hour], from: date, to: self).hour ?? 0
}
/// Returns the amount of minutes from another date
func minutes(from date: Date) -> Int {
return Calendar.current.dateComponents([.minute], from: date, to: self).minute ?? 0
}
/// Returns the amount of seconds from another date
func seconds(from date: Date) -> Int {
return Calendar.current.dateComponents([.second], from: date, to: self).second ?? 0
}
/// Returns the a custom time interval description from another date
func offset(from date: Date) -> String {
if years(from: date) > 0 { return "\(years(from: date))y" }
if months(from: date) > 0 { return "\(months(from: date))M" }
if weeks(from: date) > 0 { return "\(weeks(from: date))w" }
if days(from: date) > 0 { return "\(days(from: date))d" }
if hours(from: date) > 0 { return "\(hours(from: date))h" }
if minutes(from: date) > 0 { return "\(minutes(from: date))m" }
if seconds(from: date) > 0 { return "\(seconds(from: date))s" }
return ""
}
}
//Ref: https://stackoverflow.com/questions/28404154/swift-get-local-date-and-time
extension Date {
func localDate() -> Date {
let nowUTC = Date()
let timeZoneOffset = Double(TimeZone.current.secondsFromGMT(for: nowUTC))
guard let localDate = Calendar.current.date(byAdding: .second, value: Int(timeZoneOffset), to: nowUTC) else {return Date()}
return localDate
}
}
func getInstalledVersionNumber() -> String? {
guard let infoDictionary = Bundle.main.infoDictionary, let currentVersionNumber = infoDictionary[appVersionNumberKey] as? String else { return nil}
return currentVersionNumber
}

Making a phone call in an iOS application depending on Time of Day

I'm currently developing my own iOS application that makes a phone call from the press of a button. I'd like to make it so the button calls a different number depending on the time of day. My idea is to differentiate the numbers by UTC, since local time will depend on where the device is. For example, if it is between 00:01 and 08:00, then call 1234567. If it is between 08:01 and 16:00 UTC, then call 7654321.
Here is what I have so far:
import UIKit
var str = "Hello, playground"
typealias WorkingTime = (start: NSDate, end: NSDate)
typealias TelephoneNumber = String
enum Regions {
case Americas
case EMEA
case APAC
func serviceDeskNumberForRegion(region: Regions) -> TelephoneNumber {
switch self {
case .Americas:
return "00112341234"
case .EMEA:
return "12345678976543"
case .APAC:
return "3245678908"
}
}
}
//this is to create the dates from string
let dateformatter = NSDateFormatter()
dateformatter.dateFormat = "HH:mm"
//we're hardcoding....
struct WorkingTimesForRegion {
static let AmericanWorkingTimes = (start: dateformatter.dateFromString("00:01")!, end: dateformatter.dateFromString("08:00")!)
static let EMEAWorkingTimes = (start: dateformatter.dateFromString("08:01")!, end: dateformatter.dateFromString("16:00")!)
static let APACWorkingTimes = (start: dateformatter.dateFromString("16:01")!, end: dateformatter.dateFromString("00:00")!)
static func regionForTime(time: NSDate) -> Regions {
//
//here I should use the nscalendar to make sure all times are represented on the same timezones, like UTC. Not sure how to go about this.
//
//here I probably need to check both end and start
//a switch case would've been more elegant here but I don't see how to build it at the moment
//an option I would explore is using NSRange as they make sense in principle and can be used in switch statements.
//but I don't know if you can create ranges of dates ...
if time.compare(WorkingTimesForRegion.AmericanWorkingTimes.end) == .OrderedAscending {
return .Americas
} else if time.compare(WorkingTimesForRegion.EMEAWorkingTimes.end) == .OrderedAscending {
return .EMEA
} else if time.compare(WorkingTimesForRegion.EMEAWorkingTimes.end) == .OrderedAscending {
return .APAC
}
//just in case
return .EMEA
}
}
func serviceDeskForTime(time: NSDate) -> TelephoneNumber {
let serviceDeskInCharge = WorkingTimesForRegion.regionForTime(time)
return serviceDeskInCharge.serviceDeskNumberForRegion(serviceDeskInCharge)
}
//see if it works
serviceDeskForTime(dateformatter.dateFromString("05:00")!)
func call() -> Bool {
guard let url = NSURL(string: "tel://123456765") else { return false }
if UIApplication.sharedApplication().canOpenURL(url) {
return UIApplication.sharedApplication().openURL(url)
}
return false
}
Any help would be appreciated. Thank you!

Swift filter by NSDate object property

I would like to filter my custom objects with a date property.
For example:
class Event
{
let dateFrom: NSDate!
let dateTo: NSDate!
init(dateFrom: NSDate, dateTo: NSDate) {
self.dateFrom = dateFrom
self.dateTo = dateTo
}
}
Now i have a List of maybe 500 Events, and i just want to show the Events for a specific date. I could loop through all Objects, and create a new Array of Objects, but could i also use a filter?
Ill tried something like this:
let specificEvents = eventList.filter( { return $0.dateFrom > date } )
Where date is a NSDate Object for a specific date, but i am not able to use the > operater.
Is there an easy way to get all the events for a specific date, where the date is between the dateFrom and dateTo period?
Thanks to Martin for pointing me into the right direction.
Here is an answer if someone else is looking how to solve this in Swift:
Add an Extension to NSDate:
public func <(a: NSDate, b: NSDate) -> Bool {
return a.compare(b) == NSComparisonResult.OrderedAscending
}
public func ==(a: NSDate, b: NSDate) -> Bool {
return a.compare(b) == NSComparisonResult.OrderedSame
}
extension NSDate: Comparable { }
So you are able to use the < and > operator for NSDate comparison.
var date = dateFormatter.dateFromString(dateString)
var dateTomorrow = date?.dateByAddingTimeInterval(NSTimeInterval(60*60*24)) // add one Day
eventsToday = allEvents.filter( { return $0.dateFrom >= date && $0.dateTo < dateTomorrow} )
For Swift 3.x update :-
extension Date: Comparable{
public static func <(a: Date, b: Date) -> Bool{
return a.compare(b) == ComparisonResult.orderedAscending
}
static public func ==(a: Date, b: Date) -> Bool {
return a.compare(b) == ComparisonResult.orderedSame
}
}

Resources