Why is my data not passing between View Controllers using closure? - swift

I am trying to pass data receive from a network call to another view controller when user has clicked on a button. When making printing on the FirstVC, data is in, but when printing the result in the SecondVC, there is no more value. I don' t want to use delegate but closure instead.
Also, when trying to retain the memory cycle, an error appear...
class APIsRuler {
static var oneRecipeFound: ((OneRecipeSearch) -> ())?
}
class FirstVC: UIViewController {
func cellIsClicked(index: Int) {
APIsRuler.shared.getRecipe(from: recipeID) { (success, oneRecipe) in
if success, let oneRecipe = oneRecipe {
APIsRuler.oneRecipeFound?(oneRecipe)
self.performSegue(withIdentifier: "goToSecondVC", sender: self)
}
}
}
}
Class SecondVC: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
APIsRuler.oneRecipeFound = { result in
print(result)
}
}
}

Doing this in SecondVC
APIsRuler.oneRecipeFound = { result in
print(result)
}
and this in first
APIsRuler.oneRecipeFound?(oneRecipe)
have no inner communications , you need to read your data directly from the shared class in the secondVc after the segue or send it in
self.performSegue(withIdentifier: "goToSecondVC", sender: <#Herererere#>)
and implement prepareForSegue

Let’s think about the order in which things are happening:
class APIsRuler {
static var oneRecipeFound: ((OneRecipeSearch) -> ())? // 1
}
class FirstVC: UIViewController {
func cellIsClicked(index: Int) {
APIsRuler.shared.getRecipe(from: recipeID) { (success, oneRecipe) in
if success, let oneRecipe = oneRecipe {
APIsRuler.oneRecipeFound?(oneRecipe) // 2
self.performSegue(withIdentifier: "goToSecondVC", sender: self)
}
}
}
}
Class SecondVC: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
APIsRuler.oneRecipeFound = { result in // 3
print(result)
}
}
}
oneRecipeFound starts out life empty: it is nil.
In FirstVC, the cell is clicked. We call oneRecipeFound. It is still nil, so nothing happens.
In SecondVC, we set the value of oneRecipeFound. Now it has a value, but the call has already happened.
So unless you have a time machine in your pocket, so that you can reverse that order of events somehow, the strategy you’ve outlined is doomed to failure. Of course, if you call oneRecipeFound after setting it, it will work. For example:
Class SecondVC: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
APIsRuler.oneRecipeFound = { result in
print(result)
}
APIsRuler.oneRecipeFound?(oneRecipe) // prints
}
}

Related

Passing data between controllers using combine not working

I am starting on combine with swift, but having some hard time (had experience working in swiftui before).
So the question is how to perform certain operation:
lets say i have vc1. and I go to vc2 from there
2.then i start asynchronos network closure and come back to vc1 (by popping out vc2).
Now say i want to ge just a string from vc2's asycnrhoss clousre to vc1 when i am back to vc1.
how can i achieve this?
I want to use publisher of lets say <String, Never>
how can I subscribe in my vc1 and publish or send it from vc2 ?
I am using this approach but its not working, it never comes to code under sink.....
public class Parent {
public static let shared = Parent()
public var publisher = PassthroughSibject<String,Never>()
}
class vc1: ViewController {
func viewdidLoad() {
let subscription = Parent.shared.oublisehr.sink { (result) in
print(result)
}
}
func navigatetoVC1() {
///// some code to navigate to vc1
}
func button() {
self.navigatetoVC1
}
}
class vc2: ViewController {
func viewDidload() {
///
}
func performsomeOperation() {
someasyncoperation(completion: { result in
switch result {
case .success:
//send some data to vc1
Parent.shared.publisher.send("testdata")
case .failure:
//send some data to vc1
})
self.dismisVC2() //some method to pop out vc2
}
}
Your code is almost right, except you are using your Anycancellable inside viewdidload, so its scope is getting exhausted. So use it outside in the view controller as an optional AnyCancellable type.
Below code should work.
class vc1: ViewController {
var subscription = AnyCancellable?
func viewdidLoad() {
self.subscription = Parent.shared.oublisehr.sink { (result) in
print(result)
}
}
func navigatetoVC1() {
///// some code to navigate to vc1
}
func button() {
self.navigatetoVC1
}
}

How to pass a (changing) variable between two view controllers?

I have two view controllers. VC1 & VC2. VC1 is passing a variable which keeps changing & updating every second on VC1, in my case, it is the nearest beacon which is handled by a method in VC1.
VC1 code:
var id: Int = 0 // initializing
// send data to VC2
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard let vc2 = segue.destination as? navigationScreenVC else { return }
vc2.id2 = id
}
VC2 code:
var id2: Int = 0
override func viewDidLoad() {
super.viewDidLoad()
print(VC2)
}
It is working where it sends the first value it encounters, but not when the value keeps changing, so I want that to be sent as soon as it triggers a change.
I tried to do didSet{} but it doesn't work that way.
Use a delegate pattern.
In VC2:
protocol VC2Delegate: AnyObject {
var id2: Int { get }
}
class VC2 {
weak var delegate: VC2Delegate?
override func viewDidLoad() {
super.viewDidLoad()
print(delegate?.id2)
}
}
In VC1:
class VC1: UIViewController, VC2Delegate {
...
var id2: Int = 0
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard let vc2 = segue.destination as? navigationScreenVC else { return }
vc2.delegate = self
}
...
}
A ViewController should not be managing stuff after it stopped being visible. You should managing this in a separate class and waiting for updates from a delegate, lets say:
protocol BeaconUpdateListener : AnyObject {
func currentBeaconIdWasUpdated(to newValue: Int)
}
class BeaconManager {
struct DelegateWrapper {
weak var delegate : BeaconUpdateListener?
}
static let delegates = [DelegateWrapper]()
static var currentId : Int = -1 {
didSet {
delegates.forEach { (delegate) in
delegate.delegate?.currentBeaconIdWasUpdated(to: currentId)
}
}
}
}
Sample code, missing details. You could make your own or update this one. Now, having that data outside your UI code makes it easier to use from anywhere else, and update that code in the future. This way, you "subscribe" to id updates like this:
BeaconManager.delegates.append(OBJECT_THAT_NEEDS_TO_BE_NOTIFIED)
... update your id like this:
BeaconManager.currentId = 65421687543152
... and wait for updates like this:
class VC2 : ViewController, BeaconUpdateListener {
func currentBeaconIdWasUpdated(to newValue: Int) {
// Do stuff once i receive the update
}
// ...
}

Binding does not trigger event in ViewController using MVVM in Swift 4

I am trying to learn binding and understand the MVVM approach in Swift.
I was expecting the below example to work, essentially someEventHappened is called, this invokes the onEvent closure and my message is logged to the screen.
This does not happen however, nothing is printed and I am a little unsure as to why?
class ViewModal {
public var onEvent: (() -> Void)?
func someEventHappened() -> Void {
onEvent?()
}
}
class ViewController: UIViewController {
lazy var viewModel: ViewModal = {
let viewModal = ViewModal()
return viewModal
}()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
view.backgroundColor = .purple
viewModel.someEventHappened()
viewModel.onEvent = {
print("something happened")
}
}
}
Just swap assigning onEvent and calling someEventHappened
viewModel.onEvent = {
print("something happened")
}
viewModel.someEventHappened()
this is because you're calling onEvent handler inside someEventHappened and in viewDidLoad you first had called someEventHappened and then assigned onEvent

Why delegate method is not called?

I am trying to notify ChatViewController that a chat was deleted in MessagesViewController using a protocol, but the delegate method implemented in ChatViewController is never called.
In the navigationController hierarchy ChatViewController is on top of MessagesViewController.
protocol MessagesViewControllerDelegate:class {
func chatWasDeletedFromDatabase(chatUID: String)
}
class MessagesViewController: UITableViewController {
weak var delegate: MessagesViewControllerDelegate?
func observeChatRemoved() {
print("it is gonna be called")
//inform ChatViewController that a chat was deleted.
self.delegate?.chatWasDeletedFromDatabase(chatUID: chat.chatUID)
print("was called here") //prints as expected
}
}
class ChatViewController: JSQMessagesViewController {
var messagesVC: MessagesViewController?
override func viewDidLoad() {
super.viewDidLoad()
messagesVC = storyboard?.instantiateViewController(withIdentifier: "MessagesViewController") as! MessagesViewController
messagesVC?.delegate = self
}
}
extension ChatViewController: MessagesViewControllerDelegate {
func chatWasDeletedFromDatabase(chatUID: String) {
print("chatWasDeletedFromDatabase called") //never prints out
if self.chatSelected.chatUID == chatUID {
//popToRootViewController
}
}
It seems
weak var delegate: MessagesViewControllerDelegate?
is nil you have to set it to the ChatViewController presented instance what ever how you present it
let chat = ///
self.delegate = chat
self.navigationController?.pushViewController(chat,animated:true)
Also do
chat.messagesVC = self
as this
messagesVC = storyboard?.instantiateViewController(withIdentifier: "MessagesViewController") as! MessagesViewController
messagesVC?.delegate = self
isn't the currently presented messagesVC , so comment the above 2 lines

prevent retain cycle in Swift function pointers

How do I prevent a retain cycle when passing around functions as objects in Swift
Imagine you have a datasource object like this
import UIKit
class MagicDataSource:NSObject,UITableViewDatasource {
deinit {
println("bye mds")
}
//cant use unowned or weak here
var decorator:((cell:CustomCell)->Void)?
func tableView(tableView:UITableView,cellForRowAtIndexPath indexPath:NSIndexPath)->UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier(Identifier, forIndexPath: indexPath) as CustomCell
decorator?(cell)
return cell
}
}
And a view controller like this which has (and wants) a strong ref to that object
import UIKit
class ViewController: UIViewController {
var datasource:MagicDataSource? = MagicDataSource()
deinit {
println("bye ViewCon")
}
override func viewDidLoad() {
super.viewDidLoad()
datasource?.decorator = decorateThatThing
}
func decorateThatThing(cell:CustomCell) {
//neither of these two are valid
//[unowned self] (cell:CustomCell) in
//[weak self] (cell:CustomCell) in
cell.theLabel.text = "woot"
}
}
When you discard the view controller , the datasource will not be released and neither will the view controller as it holds a strong ref to the decorateThatThing function on the view controller.
You can stop the cycle and get the decorator to release by doing this in ViewController but it feels messy
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
datasource?.decorator = nil
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
datasource?.decorator = decorateThatThing
}
so the question is how do i declare vars and/or functions to avoid having to teardown the datasource manually so that when the view controller is discarded the associated datasource is released too.
Rather than
datasource.decorator = decorateThatThing
You can use
datasource.decorator = { [unowned self] cell in
self.decorateThatThing(cell)
}