NSObject Delegate Methods are not being called - ios

I'm building a NFC reading app. I want all my NFC implementation to be inside a separate class called NFCReader, so I can call it every time the user taps the readNFC button in my MainVC. Here is my current MainVC code:
class MainVC: ViewController {
#IBOutlet weak var readNFCButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
addTargetToReadNFCButton()
}
private func addTargetToReadNFCButton() {
readNFCButton.addTarget(self, action: #selector(readNFCButtonTouched), for: .touchUpInside)
}
#objc func readNFCButtonTouched() {
NFCReader().beginNFCReaderSession()
}
}
Here is my NFCReader class code:
class NFCReader: NSObject {
var session: NFCReaderSession?
func beginNFCReaderSession() {
session = NFCNDEFReaderSession(delegate: self, queue: nil, invalidateAfterFirstRead: true)
session!.alertMessage = "Hold your device near your tag for scanning"
session!.begin()
}
}
extension NFCReader: NFCNDEFReaderSessionDelegate {
func readerSessionDidBecomeActive(_ session: NFCNDEFReaderSession) {
print("readerSessionDidBecomeActive")
}
func readerSession(_ session: NFCNDEFReaderSession, didDetectNDEFs messages: [NFCNDEFMessage]) {
print("didDetectTags")
}
func readerSession(_ session: NFCNDEFReaderSession, didInvalidateWithError error: Error) {
print(error.localizedDescription)
}
}
The problem is that NFCReader's delegate functions are never being called. If I put the code from my NFCReader class into my MainVC class and call it directly, delegate functions are being called every time I scan my NFC tags. This code works:
class MainVC: ViewController {
#IBOutlet weak var readNFCButton: UIButton!
var session: NFCReaderSession?
override func viewDidLoad() {
super.viewDidLoad()
addTargetToReadNFCButton()
}
private func addTargetToReadNFCButton() {
readNFCButton.addTarget(self, action: #selector(readNFCButtonTouched), for: .touchUpInside)
}
#objc func readNFCButtonTouched() {
beginNFCReaderSession()
}
func beginNFCReaderSession() {
session = NFCNDEFReaderSession(delegate: self, queue: nil, invalidateAfterFirstRead: true)
session!.alertMessage = "Hold your device near your tag for scanning"
session!.begin()
}
}
extension MainVC: NFCNDEFReaderSessionDelegate {
func readerSessionDidBecomeActive(_ session: NFCNDEFReaderSession) {
print("readerSessionDidBecomeActive")
}
func readerSession(_ session: NFCNDEFReaderSession, didDetectNDEFs messages: [NFCNDEFMessage]) {
print("didDetectTags")
}
func readerSession(_ session: NFCNDEFReaderSession, didInvalidateWithError error: Error) {
print(error.localizedDescription)
}
}
Is there any way to make delegate functions work from my NFCReader class, not from MainVC? What's the mistake in my separate implementation?
Any help is appreciated!
EDITED:
I solved it by adding NFCReader property to main MainVC class:
let nfcReader = NFCReader()
And calling it like this:
#objc func readNFCButtonTouched() {
nfcReader.beginNFCReaderSession()
}
Thank you for your help!

Your NFCReader is being initialized, and then almost immediately deinitialized because you didn’t store any references to it (which is necessary to keep the object alive).
You can confirm this by putting a breakpoint in the deinit of your NFCReader class

Related

Start WKExtendedRuntimeSession (WKExtendedRuntimeObject was dealloced while running)

I have a problem when I try to start WKExtendedRuntimeSession
-[WKExtendedRuntimeSession dealloc]:285: WKExtendedRuntimeObject was dealloced while running. Invalidating the session 46EB2DE0-311C-41D0-93BE-46FE744B685A
class SessionCoordinator: NSObject, WKExtendedRuntimeSessionDelegate {
let runtimeSession = WKExtendedRuntimeSession();
override init() {
super.init()
runtimeSession.delegate = self;
runtimeSession.start();
}
...
}
I ran across the same issue. Here is my solution.
Create a simple singleton class to manage WKExtendedRuntimeSession
import Foundation
import SwiftUI
class ExtendedRunTime: NSObject, WKExtendedRuntimeSessionDelegate {
static let shared = ExtendedRunTime()
let session: WKExtendedRuntimeSession
override init() {
// Create the session object.
session = WKExtendedRuntimeSession()
super.init()
// Assign the delegate.
session.delegate = self
}
func extendedRuntimeSession(_ extendedRuntimeSession: WKExtendedRuntimeSession, didInvalidateWith reason: WKExtendedRuntimeSessionInvalidationReason, error: Error?) {
print("didInvalidateWithReason: \(reason)")
}
func extendedRuntimeSessionDidStart(_ extendedRuntimeSession: WKExtendedRuntimeSession) {
print("extendedRuntimeSessionDidStart")
}
func extendedRuntimeSessionWillExpire(_ extendedRuntimeSession: WKExtendedRuntimeSession) {
print("extendedRuntimeSessionWillExpire")
}
func start() {
session.start()
}
func stop() {
session.invalidate()
}
}
Init the singleton. You can do this in .appear() or in your WKApplicationDelegate applicationDidBecomeActive()
_ = ExtendedRunTime.shared
Start the session where it is needed
ExtendedRunTime.shared.start()
Make sure to stop it when finished
ExtendedRunTime.shared.stop()

Protocol-Delegate pattern not notifying View Controller

My Model saves data to Firestore. Once that data is saved, I'd like it to alert my ViewController so that a function can be called. However, nothing is being passed to my ViewController.
This is my Model:
protocol ProtocolModel {
func wasDataSavedSuccessfully(dataSavedSuccessfully:Bool)
}
class Model {
var delegate:ProtocolModel?
func createUserAddedRecipe(
docId:String,
completion: #escaping (Recipe?) -> Void) {
let db = Firestore.firestore()
do {
try db.collection("userFavourites").document(currentUserId).collection("userRecipes").document(docId).setData(from: recipe) { (error) in
print("Data Saved Successfully") // THIS OUTPUTS TO THE CONSOLE
// Notify delegate that data was saved to Firestore
self.delegate?.wasDataSavedSuccessfully(dataSavedSuccessfully: true)
}
}
catch {
print("Error \(error)")
}
}
}
The print("Data Saved Successfully") outputs to the console, but the delegate method right below it doesn't get called.
And this is my ViewController:
class ViewController: UIViewController {
private var model = Model()
override func viewDidLoad() {
super.viewDidLoad()
model.delegate = self
}
}
extension ViewController: ProtocolModel {
func wasDataSavedSuccessfully(dataSavedSuccessfully: Bool) {
if dataSavedSuccessfully == true {
print("Result is true.")
}
else {
print("Result is false.")
}
print("Protocol-Delegate Pattern Works")
}
}
Is there something I'm missing from this pattern? I haven't been able to notice anything different in the articles I've reviewed.
So I test your code and simulate something like that
import UIKit
protocol ProtocolModel {
func wasDataSavedSuccessfully(dataSavedSuccessfully:Bool)
}
class Model {
var delegate:ProtocolModel?
// I use this timer for simulate that firebase store data every 3 seconds for example
var timer: Timer?
func createUserAddedRecipe(
docId:String) {
timer = Timer.scheduledTimer(withTimeInterval: 3, repeats: true, block: { _ in
self.delegate?.wasDataSavedSuccessfully(dataSavedSuccessfully: true)
})
}
}
class NavigationController: UINavigationController {
var model = Model()
override func viewDidLoad() {
super.viewDidLoad()
model.delegate = self
// Call this method to register for network notification
model.createUserAddedRecipe(docId: "exampleId")
}
}
extension NavigationController: ProtocolModel {
func wasDataSavedSuccessfully(dataSavedSuccessfully: Bool) {
print(#function)
}
}
so you can see the result as image below, my delegate update controller that conform to that protocol.

Add a generic delegate to a base class in Swift

Ideally, I want to create a BaseViewController class that takes in a protocol type (of a delegate) and have a weak variable as the delegate. Something like this:
class BaseViewController<Delegate: AnyObject> {
weak var delegate: Delegate?
init(delegate: Delegate) {
self.delegate = delegate
super.init(...)
}
}
And then inherit from a view controller like so:
protocol MyDelegate: AnyObject {
func funcA()
func funcB()
}
class SomeViewController: BaseViewController<MyDelegate> {
func doSomething() {
delegate?.funcA()
}
}
This doesn't work as the compiler complains:
'BaseViewController' requires that 'MyDelegate' be a class type
How can I work this around to achieve what I need?
Thanks in advance :)
Thats because in swift protocols doesn't confirm to them selves, you can't use "MyProtocol" as concrete type confirming to protocol "MyDelegate"
What you can rather do is
protocol MyDelegate: AnyObject {
func funcA()
func funcB()
}
class BaseViewController<Delegate: MyDelegate> {
weak var delegate: Delegate?
init(delegate: Delegate) {
self.delegate = delegate
super.init(...)
//keeping OPs code as is
}
}
class SomeOtherDelegateClass: MyDelegate {
func funcA() {
//some code here
}
func funcB() {
//some code here
}
}
class SomeViewController: BaseViewController<SomeOtherDelegateClass> {
func doSomething() {
self.delegate?.funcA()
}
}
EDIT 1:
As OP mentioned in comment, he is trying to introduce a generic property in BaseViewController that will simply hold a weak reference to any instance whose class is decided/declared by Child classes of BaseViewController using generics, I am simplifying the above answer a bit
Try this
protocol MyDelegate {
func funcA()
func funcB()
}
class BaseViewController<Delegate> where Delegate: AnyObject {
weak var delegate: Delegate?
init(delegate: Delegate) {
self.delegate = delegate
super.init(...)
//keeping OPs code as is
}
}
class SomeOtherDelegateClass: MyDelegate {
func funcA() {
//some code here
}
func funcB() {
//some code here
}
}
class SomeViewController: BaseViewController<SomeOtherDelegateClass> {
func doSomething() {
self.delegate?.funcA()
}
}
protocol MyDelegate2 {
func funcABCD()
}
class SomeOtherDelegateClass2: MyDelegate2 {
func funcABCD() {
//some code here
}
}
class SomeViewController2: BaseViewController<SomeOtherDelegateClass2> {
func doSomething() {
self.delegate?.funcABCD()
}
}
TBH, I really dont see much of benefit of this design! Probably you need to revisit the code structure and see if you can come up with better code structure :)
You should set your delegate as a constraint for the generic type T in BaseViewController:
protocol MyDelegate: AnyObject {
func funcA()
func funcB()
}
class Delegated1: MyDelegate {
func funcA() { print("A1") }
func funcB() {}
}
class Delegated2: MyDelegate {
func funcA() { print("A2") }
func funcB() {}
}
class BaseViewController<T: MyDelegate>: UIViewController {
var delegate: T?
func doSomething() {
delegate?.funcA()
}
}
class SomeViewController1: BaseViewController<Delegated1> {}
class SomeViewController2: BaseViewController<Delegated2> {}
class TestClass {
let viewController1: SomeViewController1 = {
let viewController = SomeViewController1(nibName: nil, bundle: nil)
viewController.delegate = .init()
return viewController
}()
let viewController2: SomeViewController2 = {
let viewController = SomeViewController2(nibName: nil, bundle: nil)
viewController.delegate = .init()
return viewController
}()
// prints:
// A1
// A2
func myFunc() {
viewController1.doSomething()
viewController2.doSomething()
}
}

How to unit test when a view changes?

Currently I'm trying to test some simple functions:
class SampleViewController: UIViewController {
#IBOutlet weak var sampleView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
}
func hideView() {
sampleView.isHidden = true
}
func showView() {
sampleView.isHidden = false
}
}
Here are the test cases:
import XCTest
#testable import unitUITestPractice
class unitUITestPracticeTests: XCTestCase {
var sut: SampleViewController?
override func setUp() {
super.setUp()
sut = SampleViewController()
}
override func tearDown() {
sut = nil
super.tearDown()
}
func test_hideView() {
sut?.hideView()
XCTAssertNil(sut?.sampleView)
}
func test_showView() {
sut?.showView()
XCTAssertNotNil(sut?.sampleView)
}
}
The issue is I keep getting the error:
Thread 1: Fatal error: Unexpectedly found nil while unwrapping an
Optional value
when it tries and run the function in the tests case:
func hideView() {
sampleView.isHidden = true //error here
}
Does anyone know a better way to test whether a view has changed?

Sinch video doen't want to work on iOS (Swift)

So basically I want to enable Sinch Video in iOS application.
For testing purposes I've created SinchManaevger which is singleton and I instatiate it in AppDelegate:
class SinchManager: NSObject, SINClientDelegate, SINCallClientDelegate {
static let sharedInstance = SinchManager()
var client: SINClient?
func initSinchClientWithUserId(id: String) {
if client == nil {
if case .Authenticated(let currentUser, _) = SessionManager.sharedInstance.state.value {
self.client = Sinch.clientWithApplicationKey("xyz", applicationSecret: "xyz", environmentHost: "sandbox.sinch.com", userId: currentUser.username)
print("sinchClient")
print(client!)
self.client!.delegate = self
self.client!.setSupportCalling(true)
self.client!.enableManagedPushNotifications()
self.client!.start()
self.client!.startListeningOnActiveConnection()
}
}
}
func clientDidStart(client: SINClient!) {
print("clientDidStart")
self.client!.callClient().delegate = self
}
func clientDidStop(client: SINClient!) {
print("clientDidStop")
}
func clientDidFail(client: SINClient!, error: NSError!) {
print("clientDidFail")
}
func client(client: SINCallClient!, didReceiveIncomingCall call: SINCall!) {
print("didReceiveIncomingCall")
let sinchVC = SinchVC(username: currentUser.username)
let sinchNC = DNMMainNC(rootViewController: sinchVC)
sinchVC.call = call
}
}
And I've created Sinch ViewController which is initialized with username which will be called:
class SinchVC: UIViewController, SINCallDelegate {
private let videoController = SinchManager.sharedInstance.client!.videoController()
private let audioController = SinchManager.sharedInstance.client!.audioController()
private let callClient: SINCallClient
private var call: SINCall!
let username: String
private var mainView: SinchView { return view as! SinchView }
override func loadView() {
view = SinchView()
}
init(username: String) {
self.username = username
self.callClient = SinchManager.sharedInstance.client!.callClient()
super.init(nibName: nil, bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
call.delegate = self
self.mainView.videoView.addSubview(self.videoController.localView())
self.videoController.localView().contentMode = .ScaleToFill
if self.call.direction == SINCallDirection.Incoming {
self.audioController.startPlayingSoundFile(self.pathForSound("incoming.wav") as String, loop: true)
}
if self.call.details.videoOffered {
print("video offered")
self.mainView.videoView.addSubview(self.videoController.localView())
self.videoController.localView().contentMode = .ScaleToFill
}
mainView.videoView.addSubview(self.videoController.localView())
mainView.answerButton.addTarget(self, action: #selector(answer), forControlEvents: .TouchUpInside)
mainView.declineButton.addTarget(self, action: #selector(decline), forControlEvents: .TouchUpInside)
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
self.audioController.enableSpeaker()
}
func pathForSound(string: String) -> NSString {
let nsSt = NSBundle.mainBundle().resourcePath! as NSString
return nsSt.stringByAppendingPathComponent(string)
}
func answer() {
call.answer()
}
func decline() {
call.hangup()
}
func callDidEstablish(call: SINCall!) {
print("callDidEstablish")
}
func callDidEnd(call: SINCall!) {
print("callDidEnd")
}
func callDidProgress(call: SINCall!) {
print("callDidProgress")
self.audioController.startPlayingSoundFile(self.pathForSound("ringback.wav") as String, loop: true)
}
func callDidAddVideoTrack(call: SINCall!) {
print("callDidAddVideoTrack")
mainView.videoView.addSubview(self.videoController.remoteView())
}
}
Problem is when I try to call from my app to other phone with my app nothing happens (didReceiveIncomingCall delegate method doesn't get called at all)
If I try to call from my app to SinchVideo sample app then video call gets initiated normal. But when i call from SinchVideo app to my app nothing happens in my app. So probably i've forgot to add some notification or something to tell my app when the call is incoming. If you could help I would be very grateful. Thanks
EDIT: I managed to make didReceiveIncomingCall work but now call.answer isnt working. (nothing happens when call.answer is called and i see that my phone is ringing)
I am not sure what DNMMainNC does in your did recieve incoming call,
let sinchNC = DNMMainNC(rootViewController: sinchVC) What does DNMMainNC do?
sinchVC.call = call // private var?
But its looks kind of weird to set a private var call from your code, should that not be public or have a constructor like your init but with a call

Resources