Why protocol type never gets a value? - ios

I am trying to understand more protocols. I could not figure out myProtocolFunction never call and print or passing the variable as below.
I think my problem is about initialization. When I try with two viewcontroller everything is okay but MyStruct protocol instance is nil. Thanks for your help.
class ViewController: UIViewController, MyProtocol {
var myProtocolValue: String = ""
var globalInstance = "a"
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func click(_ sender: Any) {
var myStruct = MyStruct()
myStruct.myProtocol = self
}
func myProtocolFunction(passing: String) {
globalInstance = passing
print("never print")
}
}
protocol MyProtocol {
func myProtocolFunction(passing: String)
}
struct MyStruct {
var myProtocol: MyProtocol?
init() {
makeSomething()
}
func makeSomething() {
myProtocol?.myProtocolFunction(passing: "abc")
}
}

1- You need to make
var myStruct = MyStruct() // an instance variable
#IBAction func click(_ sender: Any) {
myStruct.myProtocol = self
}
2- Also you call init before even you set myProtocol as its called when you do var myStruct = MyStruct() and at that time myProtocol is nil as its not yet set
init() {
makeSomething()
}
Check full working edit
class ViewController: UIViewController, MyProtocol {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
func myProtocolFunction(passing: String) {
print(passing)
}
#IBAction func click(_ sender: Any) {
var myStruct = MyStruct(self)
}
}
protocol MyProtocol {
func myProtocolFunction(passing: String)
}
struct MyStruct {
var myProtocol: MyProtocol?
init(_ mine:MyProtocol) {
myProtocol = mine
makeSomething()
}
func makeSomething() {
myProtocol?.myProtocolFunction(passing: "abc")
}
}

Think about protocols as a type, in your struct you are basically saying
that this struct have a variable of this type ( your protocol )
and on init you are trying to call its function.
What you're missing is setting an instance of type ( your protocol )
so it can get called.
for instance :
MyStruct = MyStruct(myProtocol: ViewController())
Now your nullable protocol have a value and can call it's function in another word, protocols can't have implementation directly into them they're more like a signature contract
Read more about protocols here.

You need to create MyStruct() object out of the click action else when the scope is over your object is deinitialize.
Also, You have to call .makeSomething() function on the button click as the time of object creation delegate is nil.
var myStruct = MyStruct() // Create an instance here
#IBAction func click(_ sender: Any) {
myStruct.myProtocol = self
// Add your action
myStruct.makeSomething()
}

Related

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()
}
}

Changing Label text on main controller after modal closed swift macOS

I am using delegates to get a string value from my modal. When the modal closes I am trying to update Label text using that string. However, I am getting error: Unexpectedly found nil while implicitly unwrapping an Optional value: file. I am not sure how to fix this. I think it's happening because the view is not yet active.
import Cocoa
class ViewControllerA: NSViewController, SomeDelegate {
#IBOutlet weak var msgLabel: NSTextField!
var s: String = "";
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
func setDetails(s: String) {
self.user = s;
print("Notified", self.s) // <-- prints: Notified hello again
msgLabel.stringValue = self.s <-- DOESN'T WORK
}
func showModal() -> Void {
msgLabel.stringValue = "hello" // <--- WORKS
let cbvc: NSViewController = {
return self.storyboard!.instantiateController(withIdentifier: "ControllerBVC")
as! NSViewController
}()
self.presentAsModalWindow(cbvc);
}
#IBAction func onBtn(_ sender: Any) {
self.showModal();
}
}
protocol SomeDelegate {
func setDetails(s: String)
}
class ViewControllerB: NSViewController {
#IBOutlet weak var textF: NSTextField!
var delegate: SomeDelegate?
override func viewDidLoad() {
super.viewDidLoad()
// Do view setup here.
let vc = ViewControllerA()
self.delegate = vc
}
#IBAction func onBtn(_ sender: Any) {
DispatchQueue.main.async {
self.delegate?.setDetails(s: self.textF.stringValue)
self.dismiss("ControllerAVC")
}
}
}
You have a number of problems.
In ViewControllerB.viewDidLoad you are assigning a new instance of ViewControllerA to the delegate property. Don't do that. Your viewDidLoad method should look like this:
override func viewDidLoad() {
super.viewDidLoad()
}
In the showModal method ViewControllerA should assign itself as the delegate on ViewControllerB before ViewControllerB it is presented.
func showModal() -> Void {
let cbvc: NSViewController = {
let vc = self.storyboard!.instantiateController(withIdentifier: "ControllerBVC")
as! ViewControllerB
vc.delegate = self
return vc
}()
self.presentAsModalWindow(cbvc);
}
In the setDetails method just assign the string to your text field directly:
func setDetails(s: String) {
msgLabel.stringValue = s
}

Passing data to various view controllers via delegate

Struggling to learn the basics of passing data via delegates. I am trying to pass a string from my viewController to my viewController2 and print it. I am getting the error:
"Type ViewController2 has no member delagate" in my view controller 2.
I cannot figure out where I have gone wrong.
viewController 1:
protocol datadelagate {
func printThisString(string: String)
}
class ViewController: UIViewController {
var delegate: datadelagate?
override func viewDidLoad() {
delegate?.printThisString(string: "This was passed from first controller to second controller")
}
}
This is my viewController 2:
class ViewController2: UIViewController, datadelagate {
func printThisString(string: String) {
print(string)
}
override func viewDidLoad() {
super.viewDidLoad()
ViewController2.delagate = self
print(String.self)
}
}
If you want ViewController2 to print some value from ViewController, you might have to do it this way:
protocol datadelagate {
func printThisString(string: String)
func getStringFromVC1() -> String
}
class ViewController: UIViewController, datadelagate {
let someString: String = "From VC1"
func printThisString(string: String) {
print(string)
}
func getStringFromVC1() -> String {
return someString
}
override func viewDidLoad() {
ViewController2.delagate = self
}
}
class ViewController2: UIViewController {
var delegate: datadelagate?
override func viewDidLoad() {
super.viewDidLoad()
//This is how something from VC2 is sent to VC1's scope.
delegate?.printThisString(string: "Calling the delegate to print something from ViewController2 on first ViewController")
//The below call gets you some value from VC1. (This is what you wanted, I belive...)
print(delegate?.getStringFromVC1())
}
}
Now for some explanation:
For simple understanding, assume a delegate as a person who does some specific job (protocol).
You have a `delegate'
You ask your delegate to work with your friend, and your friend acknowledges. (assigns your delegate by You.delegate = self, where self is your friend)
Now, through your delegate, you can do something with your friend, by asking your delegate to do some job (defined in protocol).
EDIT
The code above won't work, as non-static data members are trying to be accessed without creating an instance
Working code
import UIKit
class ViewController2: UIViewController {
static let sharedInstance = ViewController2()
weak var delegate: DataDelagate?
override func viewDidLoad() {
super.viewDidLoad()
//This is how something from VC2 is sent to VC1's scope.
delegate?.printThis(string: "Calling the delegate to print something from ViewController2 on first ViewController")
//The below call gets you some value from VC1. (This is what you wanted, I belive...)
print(delegate?.getStringFromVC1() ?? "s")
}
}
class ViewController: UIViewController {
static let sharedInstance = ViewController2()
var someString: String = "From VC1"
override func viewDidLoad() {
super.viewDidLoad()
ViewController2.sharedInstance.delegate = self
}
}
extension ViewController: DataDelagate {
func printThis(string: String) {
print(string)
}
func getStringFromVC1() -> String {
return someString
}
}
protocol DataDelagate: AnyObject {
func printThis(string: String)
func getStringFromVC1() -> String
}

Protocol implementation method not calling in Swift

I am using Xcode 10.3. I have protocol which method is not calling. What is wrong?
My first view controller with protocol:
protocol MyProtocol: class {
func doGetUpdateInfo(valu1:String,value2:String);
}
class Class_A : UIViewController{
weak var myprotocolDelegate:MyProtocol? = nil;
override func viewDidLoad() {
super.viewDidLoad()
myprotocolDelegate?.doGetUpdateInfo(account_no:value1, account_title: value2)
}
}
My second view controller
class Class_B: UIViewController,UpdateBeneficiaryProtocol {
var class_a = Class_A()
override func viewDidLoad() {
super.viewDidLoad()
class_a.myprotocolDelegate = self;
}
func doGetUpdateInfo(value1: String, value2: String) {
print("not calling****")
}
}
What is the wrong with it?
Please see the below example. you are creating a new class for A but so it will not be called. you need to provide a reference for the current class
protocol MyProtocol: class {
func doGetUpdateInfo(valu1:String,value2:String);
}
class Class_A : UIViewController{
weak var myprotocolDelegate:MyProtocol? = nil;
override func viewDidLoad() {
super.viewDidLoad()
myprotocolDelegate?.doGetUpdateInfo(account_no:value1, account_title: value2)
}
func navigateToClassB() {
let classb = Class_B()
classb.class_a = self
self.navigationController?.pushViewcontroller(classb, animated:true)
}
}
And class b should be
class Class_B: UIViewController,UpdateBeneficiaryProtocol {
var class_a : Class_A?
override func viewDidLoad() {
super.viewDidLoad()
class_a.myprotocolDelegate = self;
}
func doGetUpdateInfo(value1: String, value2: String) {
print("not calling****")
}
}
Push controller Class_b as per display in method navigateToClassB.
If you face still issue comment here I will assist you.

Swift protocol is not setting the label value and is nil

I want to call the label from ViewController and set it at different points in the code for example there are 2 different functions and both of them will set the label to lets say "hi" and 2nd function will set it to "hello"
I used Swift3 protocol way just like this: Access label.text from separate class in swift
I have another init in the class so I'm not sure why it would not set the label text to new values but is nil.
I hit the connect button first.
Here is my code:
protocol HiResponder: class {
func hi()
func hello()
}
class ViewController: UIViewController, HiResponder {
#IBOutlet weak var status: UILabel!
var test: Test!
override func viewDidLoad() {
super.viewDidLoad()
test.responder = self //gives me nil fatal error: unexpectedly found nil while unwrapping an Optional value
}
#IBAction func connectBtn(_ sender: Any) {
self.test = Test(
p1: "hey",
p2: "there"
)
self.test.connect(p1: "hey", p2: "there")
}
func hi() {
status.text = "hi"
}
hello() {
status.text = "hello"
}
}
This is the class that sets the value of these responders:
class Test {
var test:TestQQ?
var p1:String?
var p2: String?
weak var responder: HiResponder?
init(p1: String, p2:String) {
self.p1 = p1
self.p2 = p2
}
init(responder: TestResponder) {
self.responder = responder
}
// Connect
func connect(p1:String, p2:String) {
//code
}
func setHello(){
responder?.hello()
}
func setHi(){
responder?.hi()
}
}
I tried to generalize my code but thats the idea. My connect function is being called in my viewDidload of viewController.
You didn't instantiate test. Since viewdidload will run first it is of course nil. Therefore, you cannot acces anything from it. What you need to do is find a spot to instantiate it first before you use it. Maybe in connectBTN you can set the responder after you create it instead lf in viewdidload
The solution to deal with the unwrapped nil Optional value is to set test as an Optional Test object.
There is another problem though. You never actually use either of the functions from the protocol. hi() or hello()
Below I used both functions from the protocol but not from another class. In fact I didn't use Test at all. Not sure what your goal was with using a different class to set the Label text.
protocol HiResponder: class {
func hi()
func hello()
}
class ViewController: UIViewController, HiResponder {
#IBOutlet weak var status: UILabel!
var num = 1
var test: Test?
override func viewDidLoad() {
super.viewDidLoad()
test?.responder = self //gives me nil fatal error: unexpectedly found nil while unwrapping an Optional value
}
#IBAction func connectBtn(_ sender: Any) {
if num % 2 == 0 {
hi()
} else {
hello()
}
num += 1
}
func hi() {
status.text = "hi"
}
func hello() {
status.text = "hello"
}
}
class Test {
var test: Test?
var p1: String?
var p2: String?
weak var responder: HiResponder?
init(p1: String, p2:String) {
self.p1 = p1
self.p2 = p2
}
init(responder: HiResponder) {
self.responder = responder
}
// Connect
func connect(p1:String, p2:String) {
//code
}
func setHello(){
responder?.hello()
}
func setHi(){
responder?.hi()
}
}

Resources