Class does not conform NSObjectProtocol [duplicate] - swift

This question already has answers here:
Type CCC doesnt conform to protocol 'NSObjectProtocol'
(3 answers)
Closed 6 years ago.
I get an error that my class doesn't conform the NSObjectProtocol, I don't know what this means. I have implemented all the function from the WCSessionDelegate so that is not the problem. Does somebody know what the issue is? Thanks!
import Foundation
import WatchConnectivity
class BatteryLevel: WCSessionDelegate {
var session: WCSession? {
didSet {
if let session = session {
session.delegate = self
session.activate()
}
}
}
var batteryStatus = 0.0;
func getBatteryLevel(){
if WCSession.isSupported() {
// 2
session = WCSession.default()
// 3
session!.sendMessage(["getBatteryLevel": ""], replyHandler: { (response) -> Void in
if (response["batteryLevel"] as? String) != nil {
self.batteryStatus = (response["batteryLevel"] as? Double)! * 100
}
}, errorHandler: { (error) -> Void in
// 6
print(error)
})
}}
func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
}
func session(_ session: WCSession, didReceiveMessage message: [String : Any]) {
}
}

See Why in swift we cannot adopt a protocol without inheritance a class from NSObject?
In short, WCSessionDelegate itself inherits from NSObjectProtocol therefore you need to implement methods in that protocol, too. The easiest way to implement those methods is to subclass NSObject:
class BatteryLevel: NSObject, WCSessionDelegate
Note that you are dealing with Obj-C APIs here.

Related

Selector type of expression is ambiguous without more context

I'm trying to get ARSessionDelegate method using selector, but im getting this error:
Type of expression is ambiguous without more context
There is mine code:
#selector(ARSessionDelegate.session(_:didUpdate:) as ((ARSessionDelegate) -> (ARSession, ARFrame) -> Void))
That's how this method looks like:
public protocol ARSessionDelegate : ARSessionObserver {
optional func session(_ session: ARSession, didUpdate frame: ARFrame)
}
And also, I am trying to make an rx extension for the ARKit session using this answer, but im not sure it's caused the problem.
Because there are multiple methods with the same selector name, you are forced to implement the method in the delegate and forward the calls using a subject. Like this:
extension ARSession: HasDelegate { }
extension Reactive where Base: ARSession {
var delegate: ARSessionDelegateProxy {
return ARSessionDelegateProxy.proxy(for: base)
}
var didUpdate: Observable<ARFrame> {
return delegate.didUpdate.asObservable()
}
}
final class ARSessionDelegateProxy
: DelegateProxy<ARSession, ARSessionDelegate>
, DelegateProxyType
, ARSessionDelegate {
init(parentObject: ARSession) {
super.init(
parentObject: parentObject,
delegateProxy: ARSessionDelegateProxy.self
)
}
deinit {
didUpdate.onCompleted()
}
public static func registerKnownImplementations() {
self.register { ARSessionDelegateProxy(parentObject: $0) }
}
func session(_ session: ARSession, didUpdate frame: ARFrame) {
didUpdate.onNext(frame)
}
fileprivate let didUpdate = PublishSubject<ARFrame>()
}

Passing Data from Delegate Swift Class to EnvironmentObject in SwiftUI Struct

How do I pass incoming data from a method triggered by a delegate in a Swift class to an EnvironmentObject?
I am aware that for this to work my Swift class needs to be called/initialized from the SwiftUI struct (be a child of the parent SwiftUI struct). However, I initialise my Swift class in the ExtensionDelegate of the Apple Watch app. I would like to see the UI Text element change when the name is updated.
The following code runs on the Apple Watch:
class User: ObservableObject {
#Published var id: UUID?
#Published var name: String?
}
//SwiftUI struct
struct UI: View {
#EnvironmentObject var userEnv: User
var body: some View {
Text(userEnv.name)
}
}
// Swift class
class WatchConnectivityProvider: NSObject, WCSessionDelegate {
static let shared = WatchConnectivityProvider()
private let session: WCSession
init(session: WCSession = .default) {
self.session = session
super.init()
}
func activateSession() {
if WCSession.isSupported() {
session.delegate = self
session.activate()
}
}
//This func gets triggered when data is sent from the iPhone
func session(_ session: WCSession, didReceiveMessage message: [String : Any], replyHandler: #escaping ([String : Any]) -> Void) {
let list = message["list"]
let jsonDecoder = JSONDecoder()
if let data = try? jsonDecoder.decode(User.self, from: list as! Data) {
// !!! This is where I would like to update the EnvironmentObject userEnv !!!
// What is the best way to do this? Remember, this class has already been initialised within the ExtensionDelegate.
}
}
}
//ExtensionDelegate of Apple Watch app, initialising the WatchConnectivityProvider
class ExtensionDelegate: NSObject, WKExtensionDelegate {
func applicationDidFinishLaunching() {
// Perform any final initialization of your application.
WatchConnectivityProvider.shared.activateSession()
}
}
Dependency Injection
One of the solutions could be to store the reference to your #EnvironmentObject globally, eg. in some dependency container.
enum Dependencies {
struct Name: Equatable {
let rawValue: String
static let `default` = Name(rawValue: "__default__")
static func == (lhs: Name, rhs: Name) -> Bool { lhs.rawValue == rhs.rawValue }
}
final class Container {
private var dependencies: [(key: Dependencies.Name, value: Any)] = []
static let `default` = Container()
func register(_ dependency: Any, for key: Dependencies.Name = .default) {
dependencies.append((key: key, value: dependency))
}
func resolve<T>(_ key: Dependencies.Name = .default) -> T {
return (dependencies
.filter { (dependencyTuple) -> Bool in
return dependencyTuple.key == key
&& dependencyTuple.value is T
}
.first)?.value as! T
}
}
}
Then you create your object like this:
Dependencies.Container.default.register(User())
And you can access it from anywhere in your code:
let user: User = Dependencies.Container.default.resolve()
user.modify()
A more detailed explanation of Dependency Injection for Swift can be found here.
Singleton
Alternatively you can use standard Singleton pattern to make your User data available globally. A more detailed explanation can be found here.
Final thoughts
Clean Architecture for SwiftUI is a good example (in my opinion at least) of how to write an iOS app in the clean way. It's a little bit complicated, but you can pick up some parts.
Here bad Jumbo code:
class ExtensionDelegate: ObservableObject, NSObject, WCSessionDelegate, WKExtensionDelegate {
var session: WCSession?
#Published var model = Model
func session(_ session: WCSession, didReceiveMessage message: [String : Any], replyHandler: #escaping ([String : Any]) -> Void) {
print(#function)
var replyValues = Dictionary<String, Any>()
replyValues["status"] = "failed"
// 2442 Bytes
if let data = message["data"] as? Data {
// push that work back to the main thread
DispatchQueue.main.async {
self.model = try? JSONDecoder().decode(Model.self, from: data)
}
if let vm = vm {
replyValues["status"] = "ok"
replyValues["id"] = vm.id.uuidString
}
}
replyHandler(replyValues)
}
...

watchSession.sendMessage works fine on simulator, times out IRL

I wrote two applications, first using transferUserInfo, which caused too much lag (I believe because it sends stuff in background). I switched to sendMessage and was very happy with the results (faster response time). When attempted to run the application on my real iPhone and Apple Watch, I received Transfer timed out. Here's the full code and example of one of the debugs:
iPhone:
// ViewController.swift
import UIKit
import Foundation
import WatchConnectivity
class WatchManager: UIViewController, WCSessionDelegate {
var counter = 0
var watchSession: WCSession? {
didSet {
if let session = watchSession {
session.delegate = self
session.activate()
}
}
}
override func viewDidLoad(){
super.viewDidLoad()
watchSession = WCSession.default
}
private func sendDict(_ dict: [String: Any]) {
self.watchSession?.sendMessage(dict, replyHandler: nil, errorHandler: {error in print(error.localizedDescription)})
}
#IBOutlet weak var transferButton: UIButton!
#IBOutlet weak var label: UILabel!
#IBAction func dataTransfer(_ sender: Any) {
sendDict(["DataKey": counter])
counter+=1
print("sent")
}
func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
print("Session activation did complete")
}
public func sessionDidBecomeInactive(_ session: WCSession) {
print("session did become inactive")
}
public func sessionDidDeactivate(_ session: WCSession) {
print("session did deactivate")
}
func session(_ session: WCSession, didReceiveMessage message: [String : Any]) {
print("phone received app context: ", message)
if let temperature = message["DataKey"] as? String {
DispatchQueue.main.async {
self.transferButton.setTitle(temperature, for: .normal)
self.label.text=temperature
}
}
}
}
Apple Watch:
// InterfaceController.swift
import WatchKit
import Foundation
import WatchConnectivity
class InterfaceController: WKInterfaceController {
var watchSession: WCSession? {
didSet {
if let session = watchSession {
session.delegate = self
session.activate()
}
}
}
#IBOutlet weak var temperatureLabel: WKInterfaceButton!
private func sendDict(_ dict: [String: Any]) {
self.watchSession?.sendMessage(dict, replyHandler: nil, errorHandler: {error in print(error.localizedDescription)})
}
#IBAction func button() {
let urg = ["DataKey":UUID().uuidString]
sendDict(urg)
print("watch sent app context \(urg)")
}
}
extension InterfaceController: WCSessionDelegate {
#if os(iOS)
public func sessionDidBecomeInactive(_ session: WCSession) { }
public func sessionDidDeactivate(_ session: WCSession) {
session.activate()
}
#endif
func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
print("Session activation did complete")
}
func session(_ session: WCSession, didReceiveMessage message: [String : Any]) {
print("watch received app context: ", message)
if let temperature = message["DataKey"] as? Int {
self.temperatureLabel.setTitle(String(temperature))
}
}
override func willActivate() {
// This method is called when watch view controller is about to be visible to user
super.willActivate()
watchSession = WCSession.default
}
override func didDeactivate() {
// This method is called when watch view controller is no longer visible
super.didDeactivate()
}
}
Debug Example:
Session activation did complete
watch sent app context ["DataKey": "AF793FC6-7A16-4D7D-9A3B-D3BB960EC9D9"]
2019-01-13 21:07:43.524717-0800 testApp WatchKit Extension[1240:1178401] [WC] -[WCSession onqueue_handleMessageCompletionWithError:withMessageID:] C385FF5F-5EA1-478B-A930-54066C2F0B0F due to WCErrorCodeTransferTimedOut -> IDSErrorTypeTimedOut -> IDSResponseTimedOut
2019-01-13 21:07:43.525001-0800 testApp WatchKit Extension[1240:1178401] [WC] -[WCSession _onqueue_notifyOfMessageError:messageID:withErrorHandler:] C385FF5F-5EA1-478B-A930-54066C2F0B0F errorHandler: YES with WCErrorCodeTransferTimedOut -> IDSErrorTypeTimedOut -> IDSResponseTimedOut
Transfer timed out.

Sending variable with watchConnectivity but can't get the picker in the watch app to update

I've been trying to send a variable to the watch from the iPhone. I've managed to send it with watchConnectivity but I can't get the picker in the watch app to update with the new variable I sent through.
Here's the code for the watch app:
import WatchKit
import Foundation
import WatchConnectivity
var bigDict = ["":""]
class InterfaceController: WKInterfaceController, WCSessionDelegate {
lazy var keys = Array(bigDict.keys)
lazy var values = Array(bigDict.values)
var pickerItems: [WKPickerItem] = []
#IBOutlet var pickerW: WKInterfacePicker!
#IBAction func pickerDidChange(_ value: Int) {
}
#IBAction func updateButton() {
for item in keys{
let pickerItem = WKPickerItem()
pickerItem.title = item
pickerItem.caption = bigDict[item]
pickerItems += [pickerItem]
}
pickerW.setItems(pickerItems)
}
//func refreshPickerItems() {
//for item in keys{
//let pickerItem = WKPickerItem()
//pickerItem.title = item
//pickerItem.caption = bigDict[item]
//pickerItems += [pickerItem]
// }
//pickerW.setItems(pickerItems)
// }
override func awake(withContext context: Any?) {
super.awake(withContext: context)
// Configure interface objects here.
//refreshPickerItems()
}
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.default
session.delegate = self
session.activate()
}
}
func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
}
func session(_ session: WCSession, didReceiveMessage message: [String : Any]) {
print(message)
bigDict = message as! [String : String]
print(bigDict)
}
}
Shouldn't you call "updateButton()" when you receive the data?
func session(_ session: WCSession, didReceiveMessage message: [String : Any]) {
print(message)
bigDict = message as! [String : String]
updateButton()
print(bigDict)
}

Class as a delegate placeholder for a struct

I have a simple struct where I use NSURLSession to get the data from the web. I would like to extend it to provide progress of downloading. I cannot use struct as a NSURLSession delegate because it needs to be a NSObject so I have created simple class where I pass to NSURLSession delegate. This is short version of my code:
struct Resource {
private var progressDelegate: ProgressSessionDelegate?
private var session: NSURLSession!
init() {
self.progressDelegate = ProgressSessionDelegate()
let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
session = NSURLSession(configuration: configuration, delegate: self.progressDelegate!, delegateQueue: nil)
}
func loadAsynchronous(callback: A? -> ()) {
session.dataTaskWithURL(resourceURL) {
data, response, error in
let json = data.flatMap {
try? NSJSONSerialization.JSONObjectWithData($0, options: NSJSONReadingOptions())
}
callback(mycalback)
}.resume()
}
}
and thats my cass which should handle the delegate methods:
class ProgressSessionDelegate: NSObject, NSURLSessionDelegate, NSURLSessionDataDelegate {
var expectedContentLength = 0
var currentLength = 0
override init() {
super.init()
}
func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveResponse response: NSURLResponse, completionHandler: (NSURLSessionResponseDisposition) -> Void) {
expectedContentLength = Int(response.expectedContentLength)
print("0: \(expectedContentLength)")
completionHandler(NSURLSessionResponseDisposition.Allow)
}
func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveData data: NSData) {
self.currentLength += data.length
let percentageDownloaded = Float(self.currentLength) / Float(self.expectedContentLength)
print("1: \(percentageDownloaded)")
}
func URLSession(session: NSURLSession, task: NSURLSessionTask, didCompleteWithError error: NSError?) {
print("Downloaded")
}
}
The problem is that the delegate methods are not called.
When I move the code from Resource and ProgressSessionDelegate struct to my ViewController and set up ViewController as a NSURLSession delegate all is working fine. I think the problem is in a way how the class is stored is struct.
Any help would be most appreciated.