Im trying to figure out how to work with delegates and protocols.
I have a MessageFetcher class which fetches messages from a url.
class MessageFetcher {
func getCurrentMessagesFromString(urlStringL: String) {..}
}
I created a protocol
protocol MessageFetcherDelegate {
func currentMessagesDidUpdate()
}
I have a View controller which displays the messages and conforms to the MessageFetcherDelegate protocol:
class MessagesViewController: UIViewController, MessageFetcherDelegate {
var messageFetcher = MessageFetcher()
var delegate: MessageFetcherDelegate?
override func viewDidLoad() {
delegate = self
}
func currentMessagesDidUpdate() {
collectionView.reloadData()
}
}
I want the view controller to be notified when the fetcher has updated the messages array and reload the collection views data.
Where am I going wrong and what do I need to add? How do I notify the controller that messages have been updated?
In your class MessageFetcher you need to add delegate property not in MessagesViewController:
class MessageFetcher {
func getCurrentMessagesFromString(urlStringL: String) {..}
var delegate: MessageFetcherDelegate?
}
And your MessagesViewController should looks like below, see comments for explanation:
class MessagesViewController: UIViewController, MessageFetcherDelegate {
var messageFetcher = MessageFetcher()
override func viewDidLoad() {
// Declare MessagesViewController class as a deleagte od message Fetcher
messageFetcher.delegate = self
}
func currentMessagesDidUpdate() {
collectionView.reloadData()
}
}
Now what left to do is call delegate method inside MessageFetcher, add this code when the fetcher has updated the messages:
delegate?.currentMessagesDidUpdate()
Related
I have a UIbutton class - from which I would like to call an 'adjust constraints' method after the user makes changes on the setting screen. I have created some protocols and all seems in order but it is not calling the method from the subview UIButton after the user closes the Setting Screen.
I have tried some of the other solutions here - that hasn't worked and I think it might be because I am using a UIButton class and I can't reinstantiate it, or call the instantiation? Either way, it never calls the method from the delegate.
Is using protocols the right way to solve this problem and if so, what am I missing?
Basically I have 3 files; the MainVC which I set as my first delegate (it gets triggered from my SettingScreenVC when user is done making changes to Setting Screen):
class MainVC: UIViewController, SettingScreenDelegate {
weak var numButtonDelegate: Buttons_Numeric?
func settingSetButtonConstraints() {
numButtonDelegate?.setupButtonConstraints()
}
}
Then in my Setting Screen I call the MainVC after the user made some changes to their settings:
class MainVC: SettingScreenVC {
weak var delegate: SettingScreenDelegate?
func closeSettings() {
delegate?.settingSetButtonConstraints()
}
}
Then in my Buttons_Numeric class I declare the function and the UIButton class delegate:
protocol numButtonDelegate: class {
func setupButtonConstraints()
}
class Buttons_Numeric: UIButton, numButtonDelegate {
weak var numButtonDelegate: Buttons_Numeric?
required init(coder aDecoder:NSCoder) { super.init(coder: aDecoder)!}
override init(frame:CGRect) {super.init(frame: frame)
self.numButtonDelegate = self
setupButtonConstraints()
}
override func awakeFromNib() {
self.numButtonDelegate = self
setupButtonConstraints()
}
func setupButtonConstraints() {
//SET UP CONSTRAINTS
}
}
Ok so couple of things you need to understand about delegates:
if you create a delegate, you need to conform to the delegate somewhere.
the conforming class should be assigned to the instance of the delegate.
With that in mind lets try to fix the code:
first the settings screen:
protocol SettingScreenDelegate: class {
func settingSetButtonConstraints()
}
class SettingScreenVC {
weak var delegate: SettingScreenDelegate?
func closeSettings() {
delegate?.settingSetButtonConstraints()
}
}
So far so good now the mainScreen should conform to the SettingScreenDelegate and be assigned to its delegate:
class MainVC: UIViewController, SettingScreenDelegate {
weak var button: Buttons_Numeric!
func openSettingsScreen() {
let settingsScreen = ... // the setting screen instanciation
settingsScreen.delegate = self // the MainVC
}
func settingSetButtonConstraints() {
self.button.setupButtonConstraints()
}
}
Now for the last step, the MainVC should have an instance of the button, then the only thing we need to do is call the function 'setupButtonConstraints' from the MainVC which means we do not need the delegate at the button.
class Buttons_Numeric: UIButton {
required init(coder aDecoder:NSCoder) { super.init(coder: aDecoder)!}
override init(frame:CGRect) {super.init(frame: frame)
setupButtonConstraints()
}
override func awakeFromNib() {
setupButtonConstraints()
}
func setupButtonConstraints() {
//SET UP CONSTRAINTS
}
}
You need to assign a value to the delegate variable. I would do it like this if the answer is always self
class Example: SomeDelegate {
lazy var someDelegate: SomeDelegate? = self
}
Otherwise you'll want to do it in the class' initializer or ViewDidLoad.
I have the below code which aims an abstraction -without being have to casting Decodables - for DataModels across the app. I wanted use these DataModels to centrelize them. This is how I far I came right now and I am kind of in dead end.
In this configuration, the code tells me that ProfileResponseDelegate cannot conform to ModelDelegate when ProfileResponseDelegate is a protocol, which makes sense.
protocol ModelDelegate: class {
associatedtype DataType: Decodable
func didReceive(data: DataType)
}
class Model<Type, Delegate: ModelDelegate> where Type == Delegate.DataType {
var data: Type?
weak var delegate: Delegate?
func requestData() { return }
}
protocol ProfileResponseDelegate: ModelDelegate where DataType == ProfileResponse {}
//throws Value of protocol type 'ProfileResponseDelegate' cannot conform to 'ModelDelegate'; only struct/enum/class types can conform to protocols
class ProfileResponseModel: Model<ProfileResponse, ProfileResponseDelegate> {
override func requestData() {
guard let data = data else {
// go to api to get data
return
}
delegate?.didReceive(data: data)
}
}
class Controller: UIViewController, ProfileResponseDelegate {
let model = ProfileResponseModel()
override func viewDidLoad() {
super.viewDidLoad()
model.delegate = self
model.requestData()
}
func didReceive(data: ProfileResponse) {
//tell view code to update regarding data
}
}
When I change ProfileResponseDelegate to be a class -without not being a delegate anymore, but anyway- the code does not allow Controller to inherit from both UIViewController and ProfileResponseDelegate reasoning a class cannot inherit from multiple classes. which again makes sense.
class ProfileResponseDelegate: ModelDelegate {
typealias DataType = ProfileResponse
func didReceive(data: ProfileResponse) {
return
}
}
class Controller: UIViewController, ProfileResponseDelegate {
let model = ProfileResponseModel()
override func viewDidLoad() {
super.viewDidLoad()
model.delegate = self
model.requestData()
}
override func didReceive(data: ProfileResponse) {
//tell view code to update regarding data
}
}
With respect to first configuration, I could not make it work. However for the second one, when Controller just inherits from ProfileResponseDelegate it works just fine.
I have to find a way to make this work -preferably the first configuration- and need your advise. Much appreciated in advance.
UPDATE
So I have removed the associatedType from the ModelDelegate and removed ProfileResponseModel. Right now code looks like this.
protocol ModelDelegate: class {
//associatedtype DataType: Decodable
func didReceive<T: Decodable>(data: T)
}
class Model<Type: Decodable> {
var data: Type?
weak var delegate: ModelDelegate?
func requestData() { return }
}
//protocol ProfileResponseDelegate: ModelDelegate where DataType == ProfileResponse {}
class ProfileResponseModel: Model<ProfileResponse> {
override func requestData() {
guard let data = data else {
// go to api to get data
return
}
delegate?.didReceive(data: data)
}
}
class Controller: UIViewController, ModelDelegate {
let model = ProfileResponseModel()
override func viewDidLoad() {
super.viewDidLoad()
model.delegate = self
model.requestData()
}
func didReceive<T>(data: T) where T : Decodable {
//I want this `data` to come as what it is.
if let response = data as? ProfileResponse {
print(type(of: response))
}
}
}
It works likes this, however my ultimate purpose for doing this to not being have to cast the data to ProfileResponse here -and in other places to other Decodable type-.
I have a AAATableViewController with tableView.
And I have a separate swift singleton file.
In a singleton, I have a function with some logic and in this logic,
I have
DispatchQueue.main.async {
self.tableView.reloadData()
}
but it shows an error because there is no tableview in a singleton class.
How can I reload AAATableViewController's tableview inside singleton using delegate and protocol?
I read some articles with delegate , but they are all between viewcontrollers, and couldn't figure out.
I am making some assumptions about the structure of your code, but I think you're going to want something like this:
protocol ReloadDelegate: AnyObject {
func reloadTable()
}
struct Singleton {
weak var reloadDelegate: ReloadDelegate?
static var shared = Singleton()
func doSomething() {
DispatchQueue.main.async {
self.reloadDelegate?.reloadTable()
}
}
}
class TableViewController: UITableViewController, ReloadDelegate {
override func viewDidLoad() {
super.viewDidLoad()
Singleton.shared.reloadDelegate = self
}
func reloadTable() {
self.tableView.reloadData()
}
}
When you create the TableViewController, you'll need to set the Singleton's reload delegate to the TableViewController. Now the Singleton knows about the TableViewController and can tell it to reload.
I'm trying to use MVVM with delegate protocols. When something changes in the view model I want to trigger it in the view controller.
When I want to use protocols to handle the view model's event on a view controller, I can not set the protocol to the view controller for my view model class.
It gives me the error:
Argument type (SecondViewController) -> () -> SecondViewController does not conform to expected type SecondViewModelEvents
How can I do this the right way?
Here is the code for my view model:
protocol SecondViewModelEvents {
func changeBackground()
}
class SecondViewModel:NSObject {
var events:SecondViewModelEvents?
init(del:SecondViewModelEvents) {
self.events = del
}
func loadDataFromServer() {
self.events?.changeBackground()
}
}
And for my view controller class:
class SecondViewController: UIViewController,SecondViewModelEvents {
let viewModel = SecondViewModel(del: self) //Argument type '(SecondViewController) -> () -> SecondViewController' does not conform to expected type 'SecondViewModelEvents'
#IBAction func buttonPressed(_ sender: Any) {
self.viewModel.loadDataFromServer()
}
func changeBackground() {
self.view.backgroundColor = UIColor.red
}
}
You're trying to initialize the view model variable and pass the view controller as a delegate which at this point is not fully initialized.
Try checking out the very informative and very detailed Initialization page in the official Swift language guide.
Since this is a protocol used for this specific purpose, we can safely constrain it to classes (notice the : class addition to your code.
protocol SecondViewModelEvents: class {
func changeBackground()
}
It's good practice to use more descriptive naming, and also using weak references for delegate objects in order to avoid strong reference cycles.
class SecondViewModel {
weak var delegate: SecondViewModelEvents?
init(delegate: SecondViewModelEvents) {
self.delegate = delegate
}
func loadDataFromServer() {
delegate?.changeBackground()
}
}
You can try to use an optional view model, which will get initialized in an appropriate place, like awakeFromNib():
class SecondViewController: UIViewController, SecondViewModelEvents {
var viewModel: SecondViewModel?
override func awakeFromNib() {
super.awakeFromNib()
viewModel = SecondViewModel(delegate: self)
}
#IBAction func buttonPressed(_ sender: Any) {
viewModel?.loadDataFromServer()
}
func changeBackground() {
view.backgroundColor = UIColor.red
}
}
Or an alternative approach would be to initialize a non-optional view model in the UIViewController required initializer:
// ...
var viewModel: SecondViewModel
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.viewModel = SecondViewModel(delegate: self)
}
// ...
You need to use lazy initialization as,
lazy var viewModel = SecondViewModel(del: self)
OR
lazy var viewModel = { [unowned self] in SecondViewModel(del: self) }()
I have a singleton to store some global data for my macOS app, one of my ViewController keeps modifying data. I want to simultaneously show the changes in a View, which is related to another ViewController. what 's the best way to do this?
Global Data:
final class AppData {
static var logs: [LogData] = []
}
ViewController 1:
class FirstViewController: NSViewController {
AppData.logs.append(newLogData)
}
ViewController 2:
class SecondViewController: NSViewController {
// what's the best way to simultaneously watch the change of AppData.logs?
}
If your App is planned to be macOS only you can use a NSObjectController. This is definitively the easiest approach and you can do most of the configuration in Interface builder. It works internally with bindings. In case of an array you want to observe, you would use a NSArrayController.
One way is to use the notificationcenter
In viewcontroller2 add:
override func viewDidLoad() {
super.viewDidLoad()
notificationCenter.default.addObserver(self,
selector: #selector(view1DidChange),
name: "view1DidChange",
object: nil
)
}
#objc private func view1DidChange(_ notification: Notification) {
// Do something
}
In viewcontroller1 add
notificationCenter.default.post(name: "view1DidChange", object: self)
This can be repeated in every class, that should listen.
Here i am sharing the Delegate & Protocol approach to achieve this functionality.
final class AppData {
static var logs: [LogData] = []
}
protocol FirstViewControllerDelegate {
func ViewControllerDelegate(appData:[LogData])
}
class FirstViewController: NSViewController {
var delegate:FirstViewControllerDelegate?
override func viewDidLoad() {
super.viewDidLoad()
AppData.logs.append(newLogData)
self. delegate?.ViewControllerDelegate(appData: AppData.logs)
}
}
class SecondViewController: NSViewController,FirstViewControllerDelegate {
var firstViewController:FirstViewController = FirstViewController()
override func viewDidLoad() {
self.firstViewController.delegate = self
}
func ViewControllerDelegate(appData:[LogData]){
//Do Update the UI
}
}