My Object:
//Object to observe
struct Text {
let savedUserHeader: String
let savedUserText: String
}
ClassA where I create the object:
//First Class
class A {
func somefunc(){
let a = Text(savedUserHeader: "testHeader", savedUserText: "testText")
}
}
In classB, I want to observe if a new object was created:
//Second Class
class B {
var text: Text? {
didSet{
headerlabel.text = text.savedUserHeader
saveUserLabel.text = text?.savedUserText
}
}
}
Delegate
You can use the Delegate pattern to observe, let create a protocol called ADelegate:
protocol ADelegate {
func didCreateText(text: Text)
}
Then, add a variable called delegate in the class A and pass a Text object into didCreateText(text:) method in the somefunc():
//First Class
class A {
var delegate: ADelegate?
func somefunc(){
let a = Text(savedUserHeader: "testHeader", savedUserText: "testText")
delegate?.didCreateText(text: a)
}
}
Next, when you create an object A in B, set a.delegate = self and implement ADelegate protocol:
//Second Class
class B: ADelegate {
var headerlabel: UILabel!
var saveUserLabel: UILabel!
var a = A()
var text: Text? {
didSet{
headerlabel.text = text?.savedUserHeader
saveUserLabel.text = text?.savedUserText
}
}
init() {
a.delegate = self
}
func didCreateText(text: Text) {
print("savedUserHeader: \(text.savedUserHeader)")
print("savedUserText: \(text.savedUserText)")
}
}
That's it! The method didCreateText(text:) will be called when an object Text is created in the somefunc() method:
let b = B()
b.a.somefunc()
Notification Center
Another solution is NotificationCenter. Let post a notification when create a Text object:
func somefunc(){
let a = Text(savedUserHeader: "testHeader", savedUserText: "testText")
NotificationCenter.default.post(name: Notification.Name("addText"), object: a)
}
And observe in the class B:
init() {
NotificationCenter.default.addObserver(self, selector: #selector(observeText(noti:)), name: Notification.Name("addText"), object: nil)
}
#objc func observeText(noti: Notification) {
if let text = noti.object as? Text {
print("savedUserHeader: \(text.savedUserHeader)")
print("savedUserText: \(text.savedUserText)")
}
}
Let test it:
let b = B()
let a = A()
a.somefunc()
You will see the result:
savedUserHeader: testHeader
savedUserText: testText
Related
I have Outlet of UITextField in the main VC and I want to access its text from another class... for more clarification please see my code below:
class ProductManagement : UIViewController, UITextFieldDelegate{
#IBOutlet weak var ProductName: UITextField!
}
and I want to read the text in below class
import Firebase
import FirebaseStorage
class XUpload {
static func UploadImage(Image : UIImage, Completion : #escaping (_ url : String)->()) {
guard let imageData = Image.pngData() else { return }
let storage = Storage.storage().reference()
let pathRef = storage.child("Images/Products")
let imageRef = pathRef.child( // I want the product name to be written here! )
imageRef.putData(imageData, metadata: nil) { (meta, error) in
imageRef.downloadURL { (url, error) in
print (url as Any)
}
}
}
}
I tried to create a custom protocol to delegate the UITextField but the problem is I couldn't conform it inside XUpload class !!
Please someone write for me how is my code should be because I'm beginner and new to Swift language.
Thank you in advance.
EDIT:
Elia Answer applied below:
ProductManagement Class + Extension
class ProductManagement : UIViewController, UITextFieldDelegate{
override func viewDidLoad() {
super.viewDidLoad()
self.ProductName.delegate = self
}
#IBOutlet weak var ProductName: UITextField!
#objc private func textFieldDidChange(_ sender: UITextField) {
NotificationCenter.default.post(name: .PnameInputText, object: nil, userInfo: ["text": sender.text!])
}
}
extension Notification.Name {
public static var PnameInputText = Notification.Name("PnameInputText")
}
XUpload Class
import Firebase
import FirebaseStorage
class XUpload {
private init() {
NotificationCenter.default.addObserver(self, selector: #selector(handle(_:)), name: .PnameInputText, object: nil)
}
public static var shared = XUpload()
var textFieldText : String = "" {
didSet {print("updated value > ", textFieldText)}
}
#objc private func handle(_ sender: Notification) {
if let userInfo = sender.userInfo as NSDictionary?, let text = userInfo["text"] as? String {
textFieldText = text
}
}
static func UploadImage(Image : UIImage, Completion : #escaping (_ url : String)->()) {
guard let imageData = Image.pngData() else { return }
let storage = Storage.storage().reference()
let pathRef = storage.child("Images/Products")
let imageRef = pathRef.child("img_"+shared.textFieldText)
print("var of textFieldText value: " + shared.textFieldText) //print nothing... empty value!!
imageRef.putData(imageData, metadata: nil) { (meta, error) in
imageRef.downloadURL { (url, error) in
print ("Image uploaded to this link >> " + url!.description)
guard let str = url?.absoluteString else { return }
Completion (str)
}
}
}
}
extension UIImage {
func upload(completion : #escaping (_ url : String) ->()) {
XUpload.UploadImage(Image: self) { (ImageURL) in completion (ImageURL)
}
}
}
As you can see there is no value given to var textFieldText by the Notification Center! why??
class ProductManagement : UIViewController, UITextFieldDelegate{
var productInputText: String = ""
#IBOutlet weak var ProductName: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
ProductName.addTarget(self, action: #selector(textFieldDidChange), for: .editingChanged)
}
#objc private func textFieldDidChange(_ sender: UITextField) {
productInputText = sender.text ?? ""
}
}
This will store your input text into a variable basically with listen edit changes.
And if I get not wrong, you use UploadImage within ProductManagement class. Add a function property to UploadImage as below
class XUpload {
static func UploadImage(Image : UIImage, productName: String, Completion : #escaping (_ url : String)->()) {
guard let imageData = Image.pngData() else { return }
let storage = Storage.storage().reference()
let pathRef = storage.child("Images/Products")
let imageRef = pathRef.child( // I want the product name to be written here! )
imageRef.putData(imageData, metadata: nil) { (meta, error) in
imageRef.downloadURL { (url, error) in
print (url as Any)
}
}
}
}
and call it in ProductManagement class like below.
XUpload.UploadImage(image: myImage, productName: productInputText, Completion: {
}
EDIT:
After getting your comment, I decided the best way do it is using Notification, you use UITextField in ProductManagement class so there is no need to handle delegate method of them in XUpload
Describe notification name
extension Notification.Name {
public static var TextChange = Notification.Name("TextChange")
}
In ProductManagement, textDidChange method post text to Notification
#objc private func textFieldDidChange(_ sender: UITextField) {
NotificationCenter.default.post(name: .TextChange, object: nil, userInfo: ["text": sender.text])
}
I convert your XUpload class to a Singleton class.
class XUpload {
private init() {
NotificationCenter.default.addObserver(self, selector: #selector(handle(_:)), name: .TextChange, object: nil)
}
public static var Shared = XUpload()
#objc private func handle(_ sender: Notification) {
if let userInfo = sender.userInfo as NSDictionary?, let text = userInfo["text"] as? String {
textFieldText = text
}
}
var textFieldText: String = "" {
didSet {
print("updated value > " , textFieldText)
}
}
static func uploadImage() {
// use updated textfield text with textFieldText
}
}
Then store a variable in ProductManagement class as a singleton object and it wil work for you. The text in textfield updated every changes in XUpload class.
class ProductManagement : UIViewController, UITextFieldDelegate{
var staticVariable = XUpload.Shared
}
I am struggling with understanding how protocols work. I have 2 files and want to use protocol to pass data... Here's what I am doing:
In ViewController.swift
protocol workingProtocol { func myFunc(strValue: String)}
class ViewController: UIViewController {
var interactor = workingProtocol
#objc func doneBtn() {
interactor.myFunc(strValue: "str")
}
}
In Interactor.swift
class Interactor {
func myFunc(strValue: String) {
print(strValue)
}
}
The data is not printing from Interactor.swift
Unfortunately I can't see how you inject interaction class, also your code has some problem with syntax. Here is how it should look:
protocol WorkingProtocol: AnyObject {
func myFunc(strValue: String)
}
final class ViewController: UIViewController {
var interactor: WorkingProtocol
#objc func doneBtn() {
interactor.myFunc(strValue: "str")
}
}
final class Interactor: WorkingProtocol {
func myFunc(strValue: String) {
print(strValue)
}
}
And how to use:
let interactor: WorkingProtocol = Interactor()
let vc = ViewController(interactor: interactor)
vc.doneBtn()
Protocols defines a blueprint of methods, properties and other requirements that suite a piece of functionality.
This is an example about how it works based on your code
protocol ProtocolName {
func functionName(strValue: String)
}
class ViewController {
var interactor: ProtocolName? = nil
#objc
fileprivate func doneBtn() {
interactor?.functionName(strValue: "Passing data to interactor using protocols")
}
}
class Interactor: ProtocolName {
func functionName(strValue: String) {
print("Showing value\n", strValue)
}
}
let interactor = Interactor()
let viewController = ViewController()
viewController.interactor = interactor
viewController.doneBtn()
Another example:
protocol ProtocolName {
func functionName(strValue: String)
}
class ViewController1 {
let interactor = Interactor1()
/// Init or viewDidLoad() if you're using ViewController classes.
init() {
interactor.delegate = self
}
}
extension ViewController1: ProtocolName {
func functionName(strValue: String) {
print("Printing the value: \(strValue)")
}
}
class Interactor1 {
var delegate: ProtocolName?
func someAction() {
delegate?.functionName(strValue: "Executed action in interactor.")
}
}
let vc = ViewController1()
vc.interactor.someAction()
Let's assume that myVar has same functionality in every implementation of view. I am trying to figure out how to declare/expose some kind of set-only property instead of assigning them n-times (with every new view created), but nothing comes to my head. How could I refactor into one line & one time assignment?
var myVar: (()-> Void)?
private func callBack() {
someClass.view1.myVar = self.myVar
someClass.view2.myVar = self.myVar
someClass.view3.myVar = self.myVar
}
// MARK: - someClass pseudocode
someClass: class {
let view1: CustomView: = CustomView
let view2: CustomView: = CustomView
let view3: CustomView: = CustomView
}
// MARK: - customView pseudocode
class CustomView: UIView {
var myVar: (()-> Void)?
}
something like this, but having all CustomViews in an array is good idea and could be implemented here as well
var a: (() -> Void)?
class CustomView: UIView {
var myVar: (() -> Void)?
}
class SomeClass {
let view1 = CustomView()
let view2 = CustomView()
let view3 = CustomView()
var myVar: (() -> Void)? {
set {
self.view2.myVar = newValue
self.view1.myVar = newValue
self.view3.myVar = newValue
}
get {
return self.myVar
}
}
}
let b = SomeClass()
b.myVar = ({print(3)})
b.view1.myVar!()
Is this what you are trying to do?
[someClass.view1, someClass.view2, someClass.view3].forEach { $0.myVar = self.myVar }
This is how I tend to deal with these issues:
class OtherClass {
var text: String?
init(text: String?) {
self.text = text;
}
}
class TestClass {
var thing1: OtherClass?
var thing2: OtherClass?
var thing3: OtherClass?
var allTheThings: [OtherClass?] { return [thing1, thing2, thing3]}
var ownText: String? {
didSet {
allTheThings.forEach { $0?.text = ownText }
}
}
}
Depending on how much you expect things to change you could make the array property a constant you set in your init rather than a computed property.
If you want to get fancy you could also do something like this for setting:
private var allTheThings: [OtherClass?] {
get {
return [thing1, thing2, thing3]
}
set {
guard newValue.count == 3 else {
//probably should put an assertion in here
return
}
thing1 = newValue[0]
thing2 = newValue[1]
thing3 = newValue[2]
}
}
init() {
self.allTheThings = Array(repeating: OtherClass(text: "Test"), count: 3)
}
How do you get a reference property to trigger a property observer?
In order to demonstrate my problem I wrote a simple MVC program with one button and one label. The button increments a counter in the model and displays the value of the counter in the label in the view controller.
The problem is that the counter increment (in the model) does not trigger the didSet observer ( in the view controller)
Here is the model file:
import Foundation
class MvcModel {
var counter: Int
var message: String
init(counter: Int, message: String) {
self.counter = counter
self.message = message
}
}
// create instance
var model = MvcModel(counter: 0, message: "" )
// counting
func incrementCounter() {
model.counter += 1
model.message = "Counter Value: \(model.counter)"
//print(model.message)
}
Here is the view controller file:
import Cocoa
class ViewController: NSViewController {
let model1 = model
var messageFromModel = model.message {
didSet {
updateDisplayCounterLabel()
}
}
// update Label
func updateDisplayCounterLabel() {
DisplayCounterLabel.stringValue = model1.message
}
// Label
#IBOutlet weak var DisplayCounterLabel: NSTextField! {
didSet {
DisplayCounterLabel.stringValue = "counter not started"
}
}
// Button
#IBAction func IncrementButton(_ sender: NSButton) {
incrementCounter()
print("IBAction: \(model1.message)")
}
}
I guess the problem is linked to reference property (as I have been able to make this program work with a model based on a struct).
I would appreciate if someone could tell me how to deal with property observers and reference property and make this kind of MVC work as I plan to use it in real programs.
You could create a delegate for MvcModel
protocol MvcModelDelegate {
func didUpdateModel(counter:Int)
}
next you add a delegate property to MvcModel
class MvcModel {
var counter: Int {
didSet {
delegate?.didUpdateModel(counter: counter)
}
}
var message: String
var delegate: MvcModelDelegate?
init(counter: Int, message: String) {
self.counter = counter
self.message = message
}
}
then you make the ViewController class conform to MvcModelDelegate and finally you set model.delegate = self into the viewDidLoad
class Controller: UIViewController, MvcModelDelegate {
let model = MvcModel(counter: 0, message: "hello")
override func viewDidLoad() {
super.viewDidLoad()
self.model.delegate = self
}
func didUpdateModel(counter: Int) {
print("new value for counter \(counter)")
}
}
In case someone is interested here is the code as suggested by Noam.
Model File:
import Foundation
protocol MvcModelDelegate {
func didUpDateModel(message: String)
}
class MvcModel {
var counter: Int
var message: String {
didSet {
delegate?.didUpDateModel(message: message)
}
}
var delegate: MvcModelDelegate?
init(counter: Int, message: String) {
self.counter = counter
self.message = message
}
}
// create instance
var model = MvcModel(counter: 0, message: "" )
// counting
func incrementCounter() {
model.counter += 1
model.message = "Counter Value: \(model.counter)"
}
}
View Controller File
import Cocoa
class ViewController: NSViewController, ModelDelegate {
// communication link to the model
var model1 = model
var messageFromModel = messageToLabel
override func viewDidLoad() {
super.viewDidLoad()
self.model1.delegate = self
}
// update display
func didUpdateModel(message: String) {
//self.Label1.stringValue = model1.message
self.Label1.stringValue = messageFromModel
}
// Label
#IBOutlet weak var Label1: NSTextField! {
didSet {
Label1.stringValue = " counter not started"
}
}
// Button
#IBAction func testButton(_ sender: NSButton) {
incrementCounter()
}
}
Why my data is lost in singleton? Please take a look:
class YtDataManager {
static var shared_instance = YtDataManager()
let apiKey = /*...*/
let /*...*/
var channelData = [NSObject:AnyObject]()
var videosArray = [[NSObject:AnyObject]]()
var playlistId: String { return self.channelData["playlistId"] as! String}
var urlStringForRequestChannelDetails: String { return String("https://www.googleapis.com/youtube/v3/channels?part=contentDetails,snippet&forUsername=\(self./*ChannelName*/)&key=\(self.apiKey)") }
var urlStringForRequestChannelVideos: String { return String("https://www.googleapis.com/youtube/v3/playlistItems?part=snippet&playlistId=\(self.playlistId)&key=\(self.apiKey)") }
func fn1() { /*...*/ }
func fn2() { /*...*/ }
// class definition continue...
Look, from one function(fn1) I'm writing data to channelData:
//...
self.channelData["title"] = snippetDict["title"]
self.channelData["playlistId"] = ((firstItemDict["contentDetails"] as! [NSObject:AnyObject])["relatedPlaylists"] as! [NSObject:AnyObject])["uploads"]
//...
and so on... From second function(fn2), I'm reading data:
//...
let targetURL = NSURL(fileURLWithPath: self.urlStringForRequestChannelVideos)
//...
Hence urlStringForRequestChannelVideos is computational property it uses playlistId (look code above).
Here I was surprised about emptiness of channedData from second function(I saw it in Debug mode, also I printed it's count to stdout outside of function). Why????
class YtFeedViewController: UIViewController {
#IBOutlet weak var menuButton:UIBarButtonItem!
override func viewDidLoad() {
super.viewDidLoad()
if self.revealViewController() != nil {
menuButton.target = self.revealViewController()
menuButton.action = "revealToggle:"
self.view.addGestureRecognizer(self.revealViewController().panGestureRecognizer())
YtDataManager.shared_instance.fn1()
print(YtDataManager.shared_instance.channelData.count) //0
YtDataManager.shared_instance.fn2() //errorness
}
self.navigationItem.title = "YouTube feed"
// Do any additional setup after loading the view.
}