Currently my MainViewController can connect to my Bluetooth module and read the data coming from it.
Now, I'm trying to read the data from another View Controller.
My Bluetooth Manager is a singleton so that it doesn't get instantiated multiple times. To read and process the data in the appropriate ViewController, I was thinking of using optional delegates. It's working fine when I get to receivedMVC(data: String) but crashes when getting to receivedUVC(data: String)
I get the following error:
[BLE_Tests.MainViewController receivedUVCWithData:]: unrecognized
selector sent to instance 0x100d0a9d0 2017-06-22 16:25:58.634682-0700
BLE_Tests[9544:2692419] * Terminating app due to uncaught exception
'NSInvalidArgumentException', reason: '-[BLE_Tests.MainViewController
**receivedUVCWithData:]: unrecognized selector sent to instance
0x100d0a9d0'
If I add the receivedUVC(data: String) to my MainViewController, it doesn't crash but doesn't call the receivedUVC from the correct ViewController.
How do I point to the correct selector?
Thank you.
MainViewController.swift
class MainViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, BluetoothDelegate {
#IBOutlet weak var peripheralListTableView: UITableView!
#IBOutlet weak var updateButton: UIButton!
let bluetoothManager = BluetoothManager.getInstance()
override func viewDidLoad() {
super.viewDidLoad()
peripheralListTableView.delegate = self
peripheralListTableView.dataSource = self
bluetoothManager.delegate = self
bluetoothManager.discover()
}
func reloadPeripheralList() {
peripheralListTableView.reloadData()
}
func receivedMVC(data: String) {
print("Hello? \(data)")
}
//MARK: - UITableViewDataSource
}
UpdateViewController.swift
class UpdateViewController: UIViewController, BluetoothDelegate {
let bluetoothManager = BluetoothManager.getInstance()
func receivedUVC(data: String) {
print("Allo: \(data)")
}
}
BluetoothManager.swift
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
let stringValue = String(data: characteristic.value!, encoding: String.Encoding.utf8)!
print("Received packet: \(stringValue)")
delegate?.receivedMVC!(data: stringValue) // Delegate method in MainViewController
delegate?.receivedUVC!(data: stringValue) // Delegate method in UpdateViewController
}
[BLE_Tests.MainViewController **receivedUVCWithData:]
This tells you that the MainViewController method receivedUVCWithData: was called, but that class do not implement it. And thats the reason why it's called:
delegate?.receivedUVC!(data: stringValue)
This call will check if the delegate exists and if so it send a message to receivedUVC that must(!) exist. So it will not crash if you call this:
delegate?.receivedUVC?(data: stringValue)
But then I ask myself why you define two different methods in your protocol? Define one mandatory (not optional) method in your protocol, implement it in both UIViewControllers and call this method in BluetoothManager.swift. Then the last set delegate get the data. If both ViewControllers exists at the same time your BluetoothManager needs a delegate1 and a delegate2 and a call to the method on both delegates.
Related
I created an outlet in ViewController class and I'd like to modify it.
In the ViewController.swift file I have
import Cocoa
class ViewController: NSViewController {
#IBOutlet var LabelText: NSTextFieldCell?
override func viewDidLoad() {
super.viewDidLoad()
}
//other things
}
I'd like to change the background color of the label. How can I do that from AppDelegate?
At first I thought I could solve this problem using a function in ViewController and calling it in AppDelegate
func changeBackground() {
LabelText.textColor = NSColor.red
}
But soon I realised that it wasn't possible unless I used a static function. Then I tried to modify the code in ViewController like that
static func changeBackground() {
LabelText.textColor = NSColor.red
}
and call this function in AppDelegate like that
ViewController.changeBackground()
In this way I can access to changeBackground() function from AppDelegate, but in ViewController it gives me an error: Instance member 'LabelText' cannot be used on type 'ViewController'
I understood that this cannot be possible because somehow I'm calling "LabelText" before it's initialised (or something like that).
I don't know much about Swift and I'm trying to understand how it works. I've been searching for the answer to my question for hours, but still I don't know how to solve this.
Solution
As Rob suggested, the solution is to use NotificationCenter.
A useful link to understand how it works: https://www.appypie.com/notification-center-how-to-swift
Anyway, here how I modified the code.
In ViewController:
class ViewController: NSViewController {
#IBOutlet var label: NSTextFieldCell!
let didReceiveData = Notification.Name("didReceiveData")
override func viewDidLoad() {
NotificationCenter.default.addObserver(self, selector: #selector(onDidReceiveData(_:)), name: didReceiveData, object: nil)
super.viewDidLoad()
}
#objc func onDidReceiveData(_ notification: Notification) {
label.textColor = NSColor.red
}
}
And then, in AppDelegate:
let didReceiveData = Notification.Name("didReceiveData")
NotificationCenter.default.post(name: didReceiveData, object: nil)
Im not sure why i am getting this nil error on optional when attempting to print. Can somebody provide any input? It doesn't make sense that I am able to output the value from the aplicationContext, however when I attempt to get value from applicationContext["hearRate"] i get nil.
func session(_ session: WCSession, didReceiveApplicationContext applicationContext: [String : Any]) {
print("didReceiveApplicationContext\n\t\(applicationContext)")
let hrValue = applicationContext["heartRate"] as? String
print(applicationContext["hearRate"])
print(hrValue)
//heartRate.text = hrValue
}
console - output
2021-03-10 18:41:04.623716-0700 Trainer+[1824:759737] Metal API Validation Enabled
session active state
didReceiveApplicationContext
["heartRate": 00BPM]
nil
Optional("00BPM")
Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value: file Trainer_/ViewController.swift, line 57
2021-03-10 18:41:04.999001-0700 Trainer+[1824:759754] Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value: file Trainer_/ViewController.swift, line 57
Update - Ignore typo, found issue with IBOutlet
func session(_ session: WCSession, didReceiveApplicationContext applicationContext: [String : Any]) {
print("didReceiveApplicationContext\n\t\(applicationContext)")
let hrValue = applicationContext["heartRate"] as? String
print(applicationContext["heartRate"])
print(hrValue)
//heartRate.text = hrValue
heartRate.text = "test"
}
2021-03-11 12:45:05.482464-0700 Trainer+[1873:817768] Metal API Validation Enabled
session active state
didReceiveApplicationContext
["heartRate": 91BPM]
Optional(91BPM)
Optional("91BPM")
Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value: file Trainer_/ViewController.swift, line 58
2021-03-11 12:45:05.887523-0700 Trainer+[1873:817966] Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value: file Trainer_/ViewController.swift, line 58
the func session(didReceiveApplicationContext), from watchconnectivity is working as intended. I believe that my problem comes from me moving the heartRate label to a 'view container'. I added the watchconnectivity at the same time that I moved the label and assumed that it would work.
This may require a different question, not sure, how can i pass data along multiple view controllers that are visible at the same time. Since I am creating the session on the ViewController, I don't yet know how to pass the data to other view controllers without passing a full 'self' reference of the ViewController, to the DetailViewController. Coming from Java and PHP, i feel like this is not good coding practice.
I am thinking about extensions/delegates, and prototypes. I understand them to a slight degree, but I am currently working on an app and my main goal is to have a working prototype. I'll come back and refactor the code fix and fix any gaping vulnerabilities. If any body with experience can provide me with any reliable resources. Tired of hitting the next page of google as i cant find the answers that I am looking for lol.
Update #3
class ViewController: UIViewController, WCSessionDelegate {
#IBOutlet weak var cont: DetailViewController!
override func viewDidLoad() {
super.viewDidLoad()
if WCSession.isSupported() {
connectivitySession = WCSession.default
connectivitySession?.delegate = self
connectivitySession?.activate()
}
}
func session(_ session: WCSession, didReceiveApplicationContext applicationContext: [String : Any]) {
print("didReceiveApplicationContext\n\t\(applicationContext)")
let hrValue = applicationContext["heartRate"] as? String
print(applicationContext["heartRate"])
print("container\n\t\(cont.heart.text)")
cont.heart.text = "test"
}
******
}
// class assigned to 'view container' in ViewController in SB
class DetailViewController: UIView {
#IBOutlet weak var heart: UILabel!
// #IBOutlet weak var hearRate: UILabel!
#IBOutlet weak var sessionTimer: UILabel!
#IBOutlet weak var currentActivity: UILabel!
#IBOutlet weak var startButton: UIButton!
#IBOutlet weak var activityTimer: UILabel!
}
Output
2021-03-11 15:22:15.294727-0700 Trainer+[1938:854826] Metal API Validation Enabled
2021-03-11 15:22:15.628977-0700 Trainer+[1938:854826] *** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<Trainer_.ViewController 0x102e04ec0> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key menuContainer.'
*** First throw call stack:
(0x1986ad9d8 0x1aca33b54 0x1985be6e4 0x19986aca8 0x19a84f6a4 0x19ab5f254 0x198598298 0x19ab5b218 0x19a856d88 0x19a857114 0x19a8577e8 0x19af56448 0x19af55c5c 0x19af56af0 0x19af67730 0x19b17aba0 0x19a434be4 0x19af19ca0 0x19af1a044 0x19aa59ab8 0x1a79d7704 0x1a79ff130 0x1a79e4e60 0x1a79fee44 0x101291528 0x1012949f0 0x1a7a23e60 0x1a7a23b28 0x1a7a23ffc 0x19862dbf0 0x19862daf0 0x19862ce9c 0x1986273e0 0x198626ba0 0x1af38f598 0x19af182f4 0x19af1d874 0x1abda7b54 0x100f75a08 0x100f75980 0x100f75a4c 0x198305568)
libc++abi.dylib: terminating with uncaught exception of type NSException
*** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<Trainer_.ViewController 0x102e04ec0> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key menuContainer.'
terminating with uncaught exception of type NSException
(lldb)
answered my own question, by chaning from uiviewcontroller to name of custom class.
This is for a basic watch connection session for iphone that uses multiple view controllers on same screen.
Delegates, extensions, etc, is not required. Will need to setup your segue, secondary controller, and connections.
class ViewController: UIViewController, WCSessionDelegate {
var variable: Any? // Your variable type
var sessionVariable: WCSession?
var secondVC: SecondViewController!
override func viewDidLoad() {
super.viewDidLoad()
// start session
if WCSession.isSupported() {
sessionVariable = WCSession.default
sessionVariable?.delegate = self
sessionVariable?.activate()
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// using segue.identifier, save live ViewController to your variable
if segue.identifier == "Storyboard Segue Identifier" {
if let destinationVC = segue.destination as? SecondViewController {
secondVC = destinationVC
}
}
}
func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
print("session active state")
}
//using dispatchqueue, to update label on main thread, use self.secondVC.variable to update.
func session(_ session: WCSession, didReceiveApplicationContext applicationContext: [String : Any]) {
let contextValue = applicationContext["context"] as? String
DispatchQueue.main.async {
self.secondVC.variable.text = contextValue
}
}
}
class HealthViewController: UIViewController {
#IBOutlet weak var varialble: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
}
}
My class:
class SelectBox {
internal static func openSelector(list:[String: String], parent:UIView){
print("iosLog HELLO")
parent.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleClick(sender:))))
}
#objc func handleClick(sender: UITapGestureRecognizer) {
print("iosLog CLICK")
}
}
Set view :
SelectBox.openSelector(list: AppDelegate.stateList, parent: bgPlaceInto)
After launch print HELLO, but after click on view i get below error :
2018-07-07 18:39:12.298322+0430 Ma[971:260558] [ChatService]: SMT:
2018-07-07 18:39:12.470392+0430
Ma[971:260525] [ChatService]: RCV: 2018-07-07 18:39:12.471851+0430
Ma[971:260591] [ChatService]: RCV:
2018-07-07 18:39:14.674675+0430 Ma[971:260392] *** NSForwarding:
warning: object 0x100a9fc70 of class 'Ma.SelectBox' does not implement
methodSignatureForSelector: -- trouble ahead Unrecognized selector
+[Ma.SelectBox handleClickWithSender:] 2018-07-07 18:39:14.675210+0430 Ma[971:260392] Unrecognized selector +[Ma.SelectBox
handleClickWithSender:]
How i can set click listener to view by class?
Thank you
Your openSelector method is static. The word self in a static context, refers to an instance of the surrounding type's meta type. In this case, SelectorBox.Type.
Obviously, SelectorBox.Type does not have a handleClick method. SelectorBox does.
You need to make the openSelector method non-static:
internal func openSelector(list:[String: String], parent:UIView){
print("iosLog HELLO")
parent.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleClick(sender:))))
}
Now self refers to the SelectorBox instance.
You can call it like this:
// declare this at class level:
let box = SelectorBox()
// call the method like this
box.openSelector()
EDIT: Your class should look like this:
class ViewControllerPage: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet var bgGenderInto: UIView!
let box = SelectBox()
override func viewDidLoad() {
super.viewDidLoad()
box.openSelector(list: AppDelegate.genderList, parent: bgGenderInto)
}
}
I create nib class :
import UIKit
class AlertController: UIView {
#IBOutlet weak var back: UIView!
#IBOutlet weak var viewMain: UIView!
class func createAlert() -> AlertController {
let myNib = UINib(nibName: "Alert", bundle: nil)
let nib = myNib.instantiate(withOwner: nil, options: nil)[0] as! AlertController
nib.back.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleBack(sender:))))
return nib
}
#objc func handleBack(sender: UITapGestureRecognizer) {
//viewMain.isHidden = true
print("LogIos => X")
}
}
And use it on another class :
let view = AlertController.createAlert()
viewMain.addSubview(view)
This work fine, but if i click on back button i get below error:
2018-06-13 12:28:03.958448+0430 Mat[50493:243526] *** Terminating
app due to uncaught exception 'NSInvalidArgumentException', reason:
'+[Mat.AlertController handleBackWithSender:]: unrecognized selector
sent to class 0x104db4e90'
But if i use below code, work fine:
#IBAction func xxx(_ sender: Any) {
print("LogIos => Y")
}
I want use UITapGestureRecognizer because i use View for custom button
Although I do not understand what you mean with "func xxx works fine" (which should not, if you use the same code as above), the problem is that:
createAlert is a static (class) function
Inside
you create an instance nib of your AlertController
your #selector refers to self, which in this case is the class itself, not the instance
Therefore, the gesture recognizer refers to the static (class) function handleBack - which you can see when you take a close look at + sign in the exception text:
reason: '+[Mat.AlertController handleBackWithSender:]: unrecognized selector
Therefore, either make handleBack a static (class func), or provide an instance to as the target (which is what you want, as I would assume)
So
nib.back.addGestureRecognizer(UITapGestureRecognizer(target: nib, action: #selector(handleBack(sender:))))
should work for you.
Remark
Nevertheless, let me allow you a design hint: Although you name it AlertController, it is not a view controller, but a UIView - this is confusing. You should clearly separate view and controller, and separate which of those handles what.
I been struggling to update my tableview through another class I made.
I then found this stackoverflow solution:
How to access and refresh a UITableView from another class in Swift
But when I follow it step by step and implement all the codes, I get the following errors:
My line:
weak var delegate: UpdateDelegate?
Gets the warning
'weak' may only be applied to class and class-bound protocol types, not 'UpdateDelegate'
And my line:
self.delegate.didUpdate(self)
Gets warning:
Instance member 'delegate' cannot be used on type 'APIgetter'
Could this be because the code is old and I'm using swift 4? else I cannot see why this should be failing. I hope you can help me :)
Update:
My Protocol:
protocol UpdateDelegate: AnyObject {
func didUpdate(sender: APIgetter)
}
Snippet from my ViewController containing the tableview:
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UpdateDelegate {
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
APIgetter.addDataFromSQL()
let updates = APIgetter()
updates.delegate = self
}
//update func
func didUpdate(sender: APIgetter) {
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
My APIgetter class in APIgetter.swift:
class APIgetter {
weak var delegate: UpdateDelegate?
class func addDataFromSQL (){
//Code to fetch data from API
//Code that comes after DispatchQueue.global & DispatchQueue.main and my result being executed
//result
self.delegate.didUpdate(self)
just update your protocol definition.
protocol UpdateDelegate: class {
// protocol body
}
or
protocol UpdateDelegate: AnyObject {
// protocol body
}
This is needed (as of Swift 4 I think) because classes are reference types and you can only use a weak reference on reference types. Not value types like structs.
UPDATE: You cannot access a property/instance member from a static function the way that you currently are. Remove the class keyword from the function and it should work.
If you want/need to use a single instance of this class throughout your application you can use a static property to make it a Singleton
class APIgetter {
static let shared: APIgetter = APIgetter()
}
Then you would be able to access it like this:
APIgetter.shared.addDataFromSQL()
You could also update the delegate in the same way before calling your function.
APIgetter.shared.delegate = self
I think in this case though I would use a Singleton without the delegate. Just use a completion handler in your function. Setting and changing the delegate on a shared instance could have some side effects if not managed carefully.