Get variable value or run function from different Object - Swift - swift

I am trying to: get the value of a few variables, as well as run some functions which live in Object A, all from Object B.
I have tried for hours now to make it work with delegates and protocols. No luck.
I can't do something like this:
var delegate: MyDelegate = ViewController()
Because it seems to create a new instance of ViewController. And I want the values from the instance that is already running.
I also cannot do:
var delegate: MyDelegate?
Because the ViewController object never responds. So I get a nil anytime I call delegate?.somefunction()
I don't want a segue between screens. I just need to start a function from another object.
I bet this is an easy fix. I just can't get it. Thanks for your help.
Some of my code:
class PauseButtonView: NSView{
var delegate: PauseButtonDelegate?
...
var result = delegate?.startFunction()
}
protocol PauseButtonDelegate {
func startFunction() -> String
}
class ViewController: NSViewController, PauseButtonDelegate {
func startFunction() -> String {
let myString = "Hello World!"
return myString
}
}

If you don't want either classes to have a reference to the other, you could use internal notifications to communicate between them:
// in your PauseButtonView
let object:[String:AnyObject] = [ "aParameter" : 42 ]
let startNotification = NSNotification(name: "startFunction:", object: object)
NSNotificationCenter.defaultCenter().postNotification(startNotification)
// in the view controller
func startFunction(notification:NSNotification)
{
let object = notification.object as? [String:AnyObject]
//...
}

Related

Should I use a bridge class for setting my delegates for my ViewController?

I can simply use delegates in my views or viewControllers, no issue there, I though that would be nice if I use a bridge class for my entire app for more control on delegates, like this:
protocol MyDelegate {
func work1()
func work2(value: Int)
func work3(value: String)
}
class MyClass {
lazy var work1Delegate: MyDelegate? = nil
lazy var work2Delegate: MyDelegate? = nil
lazy var work3Delegate: MyDelegate? = nil
}
let sharedMyClass: MyClass = MyClass()
And were I need to take action I simply use this on viewDidLoad and also conforming to MyDelegate as well:
sharedMyClass.work1Delegate = self
sharedMyClass.work2Delegate = self
sharedMyClass.work3Delegate = self
I found 2 downside in my method, first I have to type 3 line of codes like this for example for my ViewController, in the real app it would be like 10 function and 10 lines like this:
sharedMyClass.work1Delegate = self
sharedMyClass.work2Delegate = self
sharedMyClass.work3Delegate = self
And second if I just need func work2(value: Int) for another view or viewController I have to have those other 2 function as well because of conforming to my protocol.
I wanted show what I am doing and asking for solving this 2 issues, having a bridge class is a must for me, but I would like to solve those 2 minor issues.

How to define a variable function and call it from an extension file in swift?

I created an extension swift file, and in that file I wrote extension UIViewController{ ... } in my project, rather than writing all lines in every view controller. Everything worked ok. Later on I created a public func(i.e myFunc), with the same name inside each of the 30 view controllers, and having different outputs. I am trying to call this common named function from the extension file, but I get an error "Value of type 'UIViewController' has no member 'myFunc'. On the extension file, how can I call this common named function which executes different outputs on each different view controller?
Extension file Code:
extension UIViewController {
func sampleFunc {
let viewController = self.navigationController?.visibleViewController {
........
viewController.myFunc() }. *//error: Value of type 'UIViewController?' has no member 'myFunc'*
}
}
Note: I don't want to use like this:
if let viewController = self as? ViewController1 {viewController.myFunc()}
if let viewController = self as? ViewController2 {viewController.myFunc()}
if let ... 3,
4....30
Or maybe is there a way to check whether the function exists, and if it exists then execute command without receiving that kind of errors ?
use of unresolved identifier
or
value of type 'UIViewController?' has no member 'myFunc'
The viewController variable in the extension is could be any VC, couldn't it? Not just the 30 you created. You can't guarantee that any random VC will have a method called myFunc, so you can't call it in the extension.
One way to resolve this problem is to create a protocol that your 30 VCs all implement:
protocol MyFuncable : UIViewController { // please come up with a better name
func myFunc ()
}
This is an example of how you implement the protocol:
class ViewController: UIViewController, MyFuncable {
func myFunc() {
// do whatever you want...
print("myFunc executed")
}
}
Now we guarantee that everything that implements myFuncable will have a method called myFunc, so now in your extension, you can check whether the VCs are MyFuncable:
extension UIViewController {
func sampleFunc() {
// here is where the checking happens:
if let viewController = self.navigationController?.visibleViewController as? MyFuncable {
viewController.myFunc()
}
}
}
Try this code below.
extension UIViewController {
func sampleFunc() {
if let viewController = self.navigationController?.visibleViewController {
print("Class name: \(NSStringFromClass(type(of: viewController)))")
let anyObject = viewController as AnyObject
if anyObject.responds(to: #selector(anyObject.myFunc)) {
anyObject.myFunc()
}
}
}
}

Losing reference to variable while using SocketIO in swift 4

I'm new to Swift and have always been a bit messy when it comes to developing for iOS so bear with me.
So in my AppDelegate I have a variable like such
var manager = Manager()
and in didFinishLaunchingWithOptions I've got
let controller = ChatRoomViewController()
self.manager.delegate = controller
self.manager.setup()
self.manager.attemptToIdentify(user:username);
In the Manager I've got
var connectionManager = SocketManager(socketURL: URL(string: "http://0.0.0.0:8080")!)
var usersName = ""
var socket:SocketIOClient!
func setup(){
connectionManager.reconnects = true
socket = connectionManager.defaultSocket;
self.setSocketEvents();
socket.connect();
}
This works and I'm able to open up a socket which displays the username on the node.js server. Now when I navigate away from the main view to the chat controller and call
appDelegate.manager.attemptMessage(msg: message);
the console tells me that I'm no longer connected. Best I can tell, I'm losing the reference to one of my variables.
I don't think you should declare your variable in the AppDelegate.
I am using Socket.IO too in one of my app and I prefer use it from a shared instance class. I don't know if you are familar with it, but it is a common architecture in iOS development.
It is based on a singleton instance and allows you to keep instance of variables in memory during all the life of the app.
For you case, you can do the following for example:
private let _managerSharedInstance = Manager()
class Manager() {
private var connectionManager = SocketManager(socketURL: URL(string: "http://0.0.0.0:8080")!)
private var usersName = ""
private var socket:SocketIOClient!
var delegate: UIViewController?
class var shared: Manager {
return _managerSharedInstance
}
init() {
connectionManager.reconnects = true
socket = connectionManager.defaultSocket;
setSocketEvents();
socket.connect();
}
private func setSocketEvents() {
// Your socket events logic
}
func attemptToIdentify(user username: String) {
socket.emit("identify", ["username": username])
}
func attemptToSend(message: String) {
socket.emit("message", ["username": username, "message": message])
}
}
So, in your AppDelegate.didFinishLaunchingWithOptions, you can now call:
Manager.shared.attemptToIdentify(user: username)
You can call this from everywhere in your code, the shared class variable will return the instance of _managerSharedInstance.
By the way, you should set your delegate of the controller in the viewWillAppear of that controller and not in the AppDelegate.
func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
Manager.shared.delegate = self
}
You set controller variable's object in didFinishLaunchingWithOptions method to your manager's delegate. But when you navigate ChatRoomViewController on UI, you open another ChatRoomViewController object.
You should set manager's delegate in your ChatRoomViewController class' viewDidLoad method like this
class ChatRoomViewController: UIViewController{
override func viewDidLoad(){
super.viewDidLoad()
appDelegate.manager.delegate = self
}
}

why do I get "Attempted to unregister unknown __weak variable" when copying an instance variable?

I noticed this today when playing with NSOutlineView and NSTableHeaderCell, but when this specific configuration is made, an error/warning(?) is printed:
objc[2774]: Attempted to unregister unknown __weak variable at 0x1016070d0. This is probably incorrect use of objc_storeWeak() and objc_loadWeak(). Break on objc_weak_error to debug.
here's the snippet:
class Foo: NSCell {
weak var weak: NSView?
override func copy(with zone: NSZone? = nil) -> Any {
// according to NSCopying documentation:
// If a subclass inherits NSCopying from its superclass and declares
// additional instance variables, the subclass has to override copy(with:)
// to properly handle its own instance variables, invoking the superclass’s implementation first.
let copy = super.copy(with: zone) as! Foo
// this produces "Attempted to unregister unknown __weak variable"
copy.weak = self.weak
return copy
}
}
let view = NSView(frame: NSRect.zero)
let foo = Foo()
foo.weak = view
let copy = foo.copy() as! Foo
this also happens if I substitute NSCell with: NSEvent, NSImage, NSImageCell
but this doesn't happen to NSColor, NSDate, NSIndexPath
I started learning Swift without prior knowledge of Obj-C. could someone help me understand why this is? is it safe to ignore? who has the blame in this case?
This is a framework bug. It's easy to reproduce with the following crasher:
import Cocoa
class Cell: NSCell {
var contents: NSString?
override func copy(with zone: NSZone? = nil) -> Any {
let newObject = super.copy(with: zone) as! Cell
newObject.contents = contents
return newObject
}
}
func crash() {
let cell = Cell()
cell.contents = "hello world"
cell.copy() // crashes while releasing the copied object
}
crash()
When you use a weak var instead, you get the error message that you showed.
My gut feeling is that there is something in the copy implementation of NSCell (and possibly of NSEvent and NSImage) that does not handle subclassing for types that have non-trivial constructors. Accordingly, if you change let newObject = super.copy(...) with let newObject = Cell(), the crash is avoided. If your superclass's copy logic is simple enough, you should probably do that for now.
If you hit this problem, you should file a bug report separately of mine, but you can probably reuse my sample.

Pass variables from one ViewController to another in Swift

I have a calculator class, a first ViewController to insert the values and a second ViewController to show the result of the calculation. Unfortunately I get a error called "Can't unwrap Optional.None" if I click the button. I know it's something wrong with the syntax, but I don't know how to improve it.
The button in the first Viewcontroller is set to "Segue: Show (e.g. Push)" in the storyboard to switch to the secondViewController if he gets tapped.
the calculator class is something like:
class Calculator: NSObject {
func calculate (a:Int,b:Int) -> (Int) {
var result = a * b
return (result)
}
}
The Viewcontroller calls the function, inserts a/b and wants to change the label which is located in the secondviewcontroller:
class ViewController: UIViewController {
#IBAction func myButtonPressed(sender : AnyObject) {
showResult()
}
var numberOne = 4
var numberTwo = 7
var myCalc = Calculator()
func showResult () {
var myResult = myCalc.calculate(numberOne, b: numberTwo)
println("myResult is \(String(myResult))")
var myVC = secondViewController()
myVC.setResultLabel(myResult)
}
And here is the code of the secondViewController
class secondViewController: UIViewController {
#IBOutlet var myResultLabel : UILabel = nil
func setResultLabel (resultValue:Int) {
myResultLabel.text = String(resultValue)
}
init(coder aDecoder: NSCoder!)
{
super.init(coder: aDecoder)
}
In Swift, everything is public by default.
Define your variables outside the classes:
import UIKit
var placesArray: NSMutableArray!
class FirstViewController: UIViewController {
//
..
//
}
and then access it
import UIKit
class TableViewController: UITableViewController {
//
placesArray = [1, 2, 3]
//
}
The problem here is that the FirstViewController has no reference to the instance of SecondViewController. Because of this, this line:
secondViewController.setResultLabel(myResult)
does nothing (except probably causing the Can't unwrap Optional.None error). There are a few ways to solve this problem. If you are using storyboard segues you can use the -prepareForSegue method of UIViewController. Here is an example:
In FirstViewController:
override func prepareForSegue(segue: UIStoryboardSegue!,sender: AnyObject!){
//make sure that the segue is going to secondViewController
if segue.destinationViewController is secondViewController{
// now set a var that points to that new viewcontroller so you can call the method correctly
let nextController = (segue.destinationViewController as! secondViewController)
nextController.setResultLabel((String(myResult)))
}
}
Note: this code will not run as is because the function has no access to the result variable. you'll have to figure that out yourself :)
I think the issue here is, you are trying to set the UI component (here, its the label : myResultLabel)
When segue is fired from first view controller, the second view has not yet been initialized. In other words, the UI object "myResultLabel" is still nil.
To solve this, you will need to create a local string variable in second controller. Now, set that string to what you are trying to display, and finally, set the actual label in "viewDidLoad()" of the second controller.
Best Regards,
Gopal Nair.