Post

Replies

Boosts

Views

Activity

Reply to DateComponentsFormatter UnitsStyle .abbreviated in MacOS
Thanks Quinn. I'm using Xcode 13.0 Beta 5, MacOS 12.0 Beta 2, with language set to English (Aus) and region set to Aus. I've a vague recollection of the problem being around for a while, but only now needed to investigate. On my system, a new Xcode created Mac project sees the same problem, but a new iOS project (also with the import Foundation - not UIKit) does not. As I said earlier, a Playground for iOS gives the "correct" formatting, but a Playground for the Mac platform (Playground Settings) gives the "mixed" formatting. Interestingly, in an Xcode generated multi-platform project (MacOS and iOS share the same code) the iOS app also shows the incorrect formatting, i.e. mixed. I'll lodge a bug report in the morning. Regards, Michaela
Topic: App & System Services SubTopic: General Tags:
Sep ’21
Reply to Import SQLite legacy data into CoreData
The test import worked as planned, except (as mentioned by others elsewhere) for having to convert the "details" NSOrderedSet of "main" to a Swift Array for use in SwiftUI's ForEach. The import only took a second or two on an oldish iPad Pro in debug mode with Xcode 13 on a MacBook Pro (early 2015) - so that's OK. It looks like Fetch performance is good: List displays the 1000 or so "main" records, with their details, within a second - and that's on app launch. The next step is to implement the CloudKit sync, and see what happens with syncing to other devices: i.e. syncing of the largish volumes of data. Any thoughts/suggestions?
Topic: Programming Languages SubTopic: Swift Tags:
Sep ’21
Reply to Date in Swift printed is wrong
let date = Calendar.current.dateComponents(in: .current, from: Date()).date! print(date) The .date! at the end of the first line converts the date back to UTC. To format the date in local time do this: let df = DateFormatter() df.dateStyle = .full df.timeStyle = .full print(df.string(from: Date())) You have options of full, medium, long, short and none for both time and date. If this doesn't suit, you can set your own formatting df.dateFormat = "yyyy-MM-dd HH:mm:ss.SS" which right now would give 2021-09-11 10:14:23.33 in Sydney time. You adjust the formatting tokens to suit your own needs, e.g. MM-dd-yy HH:mm would give 09-11-21 10:14 Regards, Michaela
Topic: UI Frameworks SubTopic: SwiftUI Tags:
Sep ’21
Reply to Date in Swift printed is wrong
Mm, comments don't format very well. Here it is again.... let currentHour = Calendar.current.component(.hour, from: Date()) if currentHour < 12 {print("now is morning")} This gets the hour, as an Integer, from the current local time, i.e. the Calendar function converts the UTC time to the user's current timezone and summertime (or not) settings.  I suggest that you experiment with Date formatting and with Calendar in a Playground.
Topic: UI Frameworks SubTopic: SwiftUI Tags:
Sep ’21
Reply to Date in Swift printed is wrong
This is because the date is stored in Swift as UTC time, i.e. as at longitude 0.0 - which used to be called Greenwich Mean Time. It doesn't take into account your timezone, nor any Summertime adjustment. When you display the time to the user in your code, via UIKit or SwiftUI, you use a DateFormatter (in UIKit) or Text(date, style: .date) in SwiftUI and the system then recognises and applies your device's timezone (including summertime status) and language. SwiftUI has only limited date formatting capabilities, so I normally use my own formatting function e.g.in SwiftUI Text(myAppAPI.dateToString(date)) where myApAPI is a collection of useful functions I have in a class (myAppAPI) and dateToString is a function that applies the DateFormatter() function, with my applied options, of Swift. When you use print(date) no formatting is applied, therefore you get the UTC time.
Topic: UI Frameworks SubTopic: SwiftUI Tags:
Sep ’21
Reply to help me! (data sync between watch and phone)
OK, now that I understand better what you are doing, this is what I would recommend (assuming that your data volumes are not several megabytes in total): Use CoreData on both the Watch and the Phone, with a CoreData entity model that is exactly the same on both. When the watch app starts, create a new Goal (using the entity class created by Xcode in XCDataModel) and set the item name, then start counting. When the "Send to Phone" button is pressed, save (write) the new Goal to Coredata on the Watch and also send it to the phone using transferUserInfo(["AddGoal":newGoal]). This sends a CoreData object to the phone, for saving in the phone's CoreData storage. In the phone app, with the same CoreData entity model, get the newGoal from didReceiveUserInfo userInfo: func session(_ session: WCSession, didReceiveUserInfo userInfo: [String : Any]) {         guard let newGoal = userInfo["AddGoal"] as? Goal else { // "Goal" is the class generated by Xcode from your CoreData entity model, so newGoal is a CoreData object.             print("ERROR: unknown data received from Watch")             return         }         // now write newGoal to CoreData in the same way as you did on the watch     } I rarely use CoreData, so I'm not confident in writing sample code for you - but I know that this solution should work. You can then also use CoreData and SwiftUI to display a table of data on the watch (if you want). Time for me to put my feet up and relax :). Cheers, Michaela
Topic: App & System Services SubTopic: General Tags:
Sep ’21
Reply to help me! (data sync between watch and phone)
I’ll answer your questions in 3 sections: 1. DataModels and SwiftUI, 2. Watch Connectivity and 3. Saving and synchronising data. DataModels and SwiftUI A key concept of SwiftUI is that the user interface (SwiftUI Views) only interacts with the user, displaying text etc and collecting input (e.g. button presses).  All other app processing (e.g. data manipulation, data storage and retrieval) is done elsewhere in an app-specific class (typically called “DataModel”) created by the developer.  Your app essentially has two parts: the watch app and the phone app.  Each needs a DataModel - “WatchDataModel” and “PhoneDataModel” - being names that I decided.  The tasks of the WatchDataModel are to 1. Collect and aggregate (?) titles and counts, 2. Transmit those to the phone 3. Maintain a local copy of the collected data 4. Prepare data for displaying to the user.  The PhoneDataModel has to 1. Receive aggregated data from the watch 2. Store those data locally, synchronising as required with existing data (?) 3. Prepare data for displaying to the user, i.e. creating a property that a SwiftUI View can refer to for displaying on the screen. In my sample code, each data model is created as a singleton - meaning that there is only ever one copy created, no matter how many times it gets referred to elsewhere within your app. class WatchDataModel : NSObject, WCSessionDelegate, ObservableObject {     static let shared = WatchDataModel() // when referring to WatchDataModel.shared only one version is ever created     let session = WCSession.default // this essentially creates a shortcut to WCSession.default Watch Connectivity Within each data model, the connectivity to/from the watch is provided by the WatchConnectivity framework and the DataModel’s class has to adopt the WCSessionDelegate protocol for this to work.  import WatchConnectivity // the Apple code to control the watch-phone connection class WatchDataModel : NSObject, WCSessionDelegate, ObservableObject { // WCSessionDelegate allows this class to be "delegated" certain tasks from WCSession When your app starts up (Watch or Phone), it needs to initialise its data model, which then needs to tell the connectivity controller (WCSession.default) to delegate certain tasks (e.g. in PhoneDataModel, deal with incoming data) to the data model.  You can refer to this controller directly as WCSession.default, but typically one creates a static property let session = WCSession.default and then refer to that property.  Before sending / receiving, you have to activate a session (in fact, you should really check that a session is active before attempting to send anything). if WCSession.isSupported() { // checks if this device is capable of watch-phone communication (e.g. an iPad is not)             session.delegate = self // allows this DataModel (class) to process tasks delegated (assigned) to it by WCSession             session.activate() // activates a session so that communication can occur         } else {             print("ERROR: Watch session not supported")         } As I’ve said previously, there’s a number of ways of sending / receiving data:  the choice depends on the volume and type of data, and the urgency of the communication.  The one I usually use is transferUserInfo, because of its reliability under various situations. transferUserInfo sends a dictionary of property list values e.g. “Title”:”Hello World” or “Count” : 21 - the first part is the key and the second is the actual value.  But there are various ways of constructing a Dictionary, especially with multiple values.  Ref https://developer.apple.com/documentation/swift/dictionary. I don’t know enough about the purpose of your app and the volume / complexity of the data for me to make any suggestion, apart from my previous idea of a struct:   struct CountData {     var title : String = ""     var tCounts = [Int]()  // an array of counts     var creationDate = Date() } data.session.transferUserInfo(["CountData":data.countData]) // in the watch's ContentView we send a CountData struct (in data.countData) with a key of "CountData" via transferUserInfo to the phone Importantly, when your phone app receives the dictionary, you must be able to access the dictionary values and do whatever you need to do. func session(_ session: WCSession, didReceiveUserInfo userInfo: [String : Any]) { // WCSession has delegated to this PhoneDataModel the task of receiving and processing user info         guard let countData = userInfo["CountData"] as? CountData else { // check that what has been received is the info that was sent. In this example a struct we defined, CountData containing title, counts and date, was sent with a key "CountData"             print("ERROR: unknown data received from Watch")             return         }         // now do something with the received CountData     } Saving and Synchronising Data Having received the watch data on the phone via didReceiveUserInfo you now have to do something with it. But what? I can think of at least 6 different ways of saving and synchronising your data (i.e. making sure the watch and phone have identical sets of data). But, again, I don’t know enough about the purpose of your app and the volume / complexity of the data for me to make any recommendation. Your idea of UserDefaults could be problematic because of your data characteristics and the limits of UserDefaults. Ref https://developer.apple.com/documentation/foundation/userdefaults I normally use SQLIte instead of CoreData, but that's only because I'd been using SQL databases for decades before I came to Apple development and I find SQL's queries, views, joins and indexes far more flexible - and within my knowledge base. I'll have to finish here: I'm tired (: Regards, Michaela
Topic: App & System Services SubTopic: General Tags:
Sep ’21
Reply to help me! (data sync between watch and phone)
Further to my last comment re your transferUserInfo problem, put the following struct definition (or something similar depending on your specific needs) in each of your data model files (WatchDataModel & PhoneDataModel) before the class definition struct CountData {     var title : String = ""     var tCounts = [Int]()  // an array of counts     var creationDate = Date() } In your watch app Extension code create an instance of CountData e.g. var countData = CountData() somewhere in WatchDataModel, then (as required) add your title, the various counts and the date created. For the counts, you can do countData.tCounts.append(integer) each time you determine a count. The date/time is already set when you create the countData, but it might be best to update it when you know you've collected all counts e.g. countData.creationDate = Date() Then in your ContentView (Send button) use data.session.transferUserInfo(["CountData":data.countData]) In your PhoneDataModel replace the didReceiveUserInfo function with: func session(_ session: WCSession, didReceiveUserInfo userInfo: [String : Any]) {         guard let countData = userInfo["CountData"] as? CountData else {             print("ERROR: unknown data received from Watch")             return         }         // now do something with the received CountData     } If needs be, you can send an array of CountData structs from the watch i.e. multiple titles with their counts. If you need to do this I'll show you how, assuming that you've worked out the code to create the array. Regards, Michaela
Topic: App & System Services SubTopic: General Tags:
Sep ’21
Reply to help me! (data sync between watch and phone)
Here's a working version for SwiftUI. I haven't implemented storage in UserDefaults nor CoreData, so as to keep the solution brief for posting here. Also, in your watch app you'll probably want to disable the Send button after the user has sent and before more items are counted: i.e. prevent duplicate sending of data to the phone. It's probably best to create a new Xcode project, e.g. WatchDemo, and specify SwiftUI with a SwiftUI interface. Then, in the WatchKIt Extension part of the project create a Swift WatchOS file (Xcode) containing the following code: import Foundation import WatchConnectivity class WatchDataModel : NSObject, WCSessionDelegate, ObservableObject {     static let shared = WatchDataModel()     let session = WCSession.default     @Published var watchCount : Int = 0     override init() {         super.init()         if WCSession.isSupported() {             session.delegate = self             session.activate()         } else {             print("ERROR: Watch session not supported")         }     }     func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {         if let error = error {             print("session activation failed with error: \(error.localizedDescription)")             return         }     } } The WatchKit Extension ContentView for this demo app is the following: import SwiftUI struct ContentView: View {     @ObservedObject var data = WatchDataModel.shared     var body: some View {         VStack{             HStack{                 Button("Count Me") {                     data.watchCount += 1                 } .foregroundColor(.red)                 Text(" ==> \(data.watchCount)")             }             Button("Send to Phone") {                 data.session.transferUserInfo(["WatchCount":data.watchCount])             } .foregroundColor(.blue)         }     } } In the Phone app part (group) of your Xcode project, create an iOS file containing: import Foundation import WatchConnectivity class PhoneDataModel : NSObject, WCSessionDelegate, ObservableObject {     static let shared = PhoneDataModel()     let session = WCSession.default     @Published var watchCount : Int = 0     override init() {         super.init()         if WCSession.isSupported() {             session.delegate = self             session.activate()         } else {             print("ERROR: Watch session not supported")         }     }     func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {         if let error = error {             print("session activation failed with error: \(error.localizedDescription)")             return         }     }     func sessionDidBecomeInactive(_ session: WCSession) {         session.activate()     }     func sessionDidDeactivate(_ session: WCSession) {         session.activate()     }     func session(_ session: WCSession, didReceiveUserInfo userInfo: [String : Any]) {         guard let newCount = userInfo["WatchCount"] as? Int else {             print("ERROR: unknown data received from Watch")             return         }         DispatchQueue.main.async {             self.watchCount = newCount // resets count to be last sent count from watch         }     } } The Phone app's ContentView for this demo app is: import SwiftUI struct ContentView: View {     @ObservedObject var data = PhoneDataModel.shared     var body: some View {         Text("Count received from watch = \(data.watchCount)")             .padding()     } } NOTE that with this method of sending data from the watch, the Phone app does not need to be active, nor the phone nearby etc. Transfer data are queued until the phone app is launched and a session established. If you want to send data to the watch, then add a func session(_ session: WCSession, didReceiveUserInfo userInfo: function to the WatchDataModel and send data from the phone by calling data.session.transferUserInfo at an appropriate point (where data = PhoneDataModel.shared) Once you've got your mind around how this demo app works, you should be able to construct your own, or modify this one. Good luck and best wishes, Michaela
Topic: App & System Services SubTopic: General Tags:
Sep ’21
Reply to Is it possible to use WidgetKit to read Bluetooth device information, such as battery power?
Your question has sparked my interest: I have a number of BLE enabled apps, where battery level is of importance. Currently (pardon the pun) I check the level on app launch, but I can see the benefit of your approach in some of my circumstances. From reading the WidgetKit documentation I see no reason that a CoreBluetooth enabled class cannot be instantiated and executed within a TimelineProvider, assuming that the periodicity restrictions of WidgetKit are met. Any solution might need to use Combine to deal with the asynchronous nature of getting bluetooth responses. However, whether this would work with non-BLE (via External Accessory) I'm not so sure. Also, I'm not clear what you mean by "I don't want to use the battery power of the system". The Widget Extension will use the App device's resources to communicate with your bluetooth devices, but only at the time intervals (hourly?) set in the Timeline so it shouldn't be too much of a drain. If I get time I might experiment with one of my devices. Regards, Michaela
Topic: UI Frameworks SubTopic: SwiftUI Tags:
Sep ’21
Reply to Update App Content Data
If I were doing your project I would use CloudKit, as described here https://developer.apple.com/icloud/cloudkit/ Using your own website would be feasible and initially might appear to be simpler, but you would have the problem of automatically notifying all app installations of new content (possible but not easy). CloudKit has this capability built in. It also deals with the problem of updating/synchronising local storage of the data: important if the app needs to be useable during periods without an internet connection and/or to avoid reloading all data each time the app is launched. There's quite a lot to learn with this approach, but I'm sure it will be worth it :) I would also build a Multiplatform app with SwiftUI, so that the app would be useable across all Apple platforms with a substantial amount of common code. Best wishes, Michaela
Sep ’21
Reply to help me! (data sync between watch and phone)
The key documentation is here https://developer.apple.com/documentation/watchconnectivity/wcsession In essence, you need a class (can be the AppDelegate) in your iOS app that implements the WCSessionDelegate protocol, establishes a WCSession and then processes incoming watch data via func session(_ session: WCSession, didReceiveUserInfo userInfo: [String : Any]). In the watch app Extension you need to create a class that adopts WCSessionDelegate, then when required create and activate a session within that class, then the class sends the information (title, number) to the phone using session.transferUserInfo(obsDict) - where session is the WCSession you instantiated and activated. In each case, don't forget to assign the delegate. There are various methods of sending data between the watch and the phone, but the above is probably the best suited to your needs. This is what I mostly use, especially if the phone app is not active at the time the watch sends the data. There's also some other housekeeping to do, such as checking that a session is active: the documentation describes this. Good luck, Michaela
Topic: App & System Services SubTopic: General Tags:
Sep ’21