How to reload Container A after update Container B? - swift

I have a ViewController containing a Segmented Control of 3 ContainerViews. Each Container is a ViewController containing a tableView. In CV2, I can choose a row. This row is deleted from CV2 and should be added in CV1.
Before use Segmented Controller, I used tab and table view on each tab were correctly updated. Now, with ContainerView, I don't know how to call the reload. This is my action on segment :
#IBAction func bSegment(_ sender: UISegmentedControl) {
if sender.selectedSegmentIndex == 0 {
UIView.animate(withDuration: 0.0, animations: {
self.listBOutlet.alpha = 1
self.addBOutlet.alpha = 0
self.delBOutlet.alpha = 0
})
} else {
if sender.selectedSegmentIndex == 1 {
UIView.animate(withDuration: 0.0, animations: {
self.listBOutlet.alpha = 0
self.addBOutlet.alpha = 1
self.delBOutlet.alpha = 0
})
} else {
if sender.selectedSegmentIndex == 2 {
UIView.animate(withDuration: 0.0, animations: {
self.listBOutlet.alpha = 0
self.addBOutlet.alpha = 0
self.delBOutlet.alpha = 1
})
}
}
}
}
I tried many things like this to access to viewWillAppear in CV2 :
print(self.children)
var myClass : ListeBEtb = self.????
myClass.ListeB.reloadData()
myClass.viewWillAppear(false)
But I don't know how to call ListeBEtb (CV1) to update it.
In my CV2 :
override func viewWillAppear(_ animated: Bool) {
let recupListeBAddModel = RecupListeBAddModel()
recupListeBAddModel.delegate = self
recupListeBAddModel.downloadItems(id: idEtablissement)
self.ListeB.reloadData()
}
Do you any idea about it ?
Thanks

You have a couple of ways of making this work, with the simplest one using the notification center for start.
Delegate pattern is the other, RxSwift is also an alternative, etc.
For using the notification center, first, define an extension for easier notification handling:
extension Notification.Name {
static let rowDeleted = Notification.Name("rowDeleted")
}
After that, subscribe the the newly created notification in CV1:
NotificationCenter.default.addObserver(self, selector: #selector(rowDeleted), name: .rowDeleted, object: nil)
Note that you need a method called rowDeleted in CV1, or however you want to call it.
Also, make sure to remove yourself as an observer on deinit of CV1:
deinit {
NotificationCenter.default.removeObserver(self)
}
Then, when you make an update in CV2, use something like this:
NotificationCenter.default.post(name: .rowDeleted, object: nil)
This is all assuming you don't need to pass the actual object, but just need an event notification. Object passing is a bit different and better done with delegate pattern, but this is a decent start.

If you're trying to call some logic that exists in the viewWillAppear(_:) function, why not extract that into its own function, call that new function from viewWillAppear(_:), and then you can call that function again when you transition your container views.
So in your container view, it would now look something like this.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
reloadDataModel()
}
func reloadDataModel() {
let recupListeBAddModel = RecupListeBAddModel()
recupListeBAddModel.delegate = self
recupListeBAddModel.downloadItems(id: idEtablissement)
self.ListeB.reloadData()
}
And then, in your transition code, perhaps just before your animation code, you can call listBOutlet.reloadDataModel(), so it can do the operation as the animation is in progress.

Related

Update tableview instead of entire reload when navigating back to tableview View Controller

I have a home UIViewController that contains a UITableView. On this view controller I display all games for the current user by reading the data from firebase in ViewWillAppear. From this view controller a user can press a button to start a new game and this button takes them to the next view controller to select settings, this then updates the data in firebase and adds a new child. Once they navigate back to the home view controller is there anyway to just update the data with the new child added instead of loading all the games for the table view again as I am currently doing?
This is my current code:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if let currentUserID = Auth.auth().currentUser?.uid {
let gamesRef = Database.database().reference().child("games").child(currentUserID)
self.games = []
gamesRef.observeSingleEvent(of: .value, with: { snapshot in
for child in snapshot.children {
let game = child as! DataSnapshot
self.games.append(game)
self.tableView.reloadData()
}
})
}
}
I think you can use observeSingleEvent and .childAdded
You can do the loading of all the data in viewDidLoad and of single child in viewWillAppear since viewDidLoad will be called once initially
Since both methods will be called initially, so we can have a bool flag so we can control which code runs initially and which does not , since viewWillAppear is called after viewDidLoad so we change the value of this flag in viewWillAppear method and then control the execution of code inside viewWillAppear using this flag
class SomeVC: UIViewController {
var flag = false
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if flag {
//do your work here
}else {
flag = true
}
}
}
Edited:
Another solution can be that you dont do anything in viewDidLoad and do the work only in viewWillAppear since in this particular scenario data in both calls are related (fetching the data from Firebase)
class SomeVC: UIViewController {
var flag = false
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if flag {
//fetch only one child
}else {
//fetch all the data initially
flag = true
}
}
}

Way to check data modification across many viewcontroller without using global variables?

I have an app which contains several viewControllers. On the viewDidAppear() of the first VC I call a set of functions which populate some arrays with information pulled from a database and then reload table data for a tableView. The functions all work perfectly fine and the desired result is achieved every time. What I am concerned about is how often viewDidAppear() is called. I do not think (unless I am wrong) it is a good idea for the refreshing functions to be automatically called and reload all of the data every time the view appears. I cannot put it into the viewDidLoad() because the tableView is part of a tab bar and if there are some modifications done to the data in any of the other tabs, the viewDidLoad() will not be called when tabbing back over and it would need to reload at this point (as modifications were made). I thought to use a set of variables to check if any modifications were done to the data from any of the other viewControllers to then conditionally tell the VDA to run or not. Generally:
override func viewDidAppear(_ animated: Bool) {
if condition {
//run functions
} else{
//don't run functions
}
}
The issue with this is that the data can be modified from many different viewControllers which may not segue back to the one of interest for the viewDidAppear() (so using a prepareForSegue wouldn't work necessarily). What is the best way to 'check' if the data has been modified. Again, I figured a set of bool variables would work well, but I want to stay away from using too many global variables. Any ideas?
Notification Center
struct NotificationName {
static let MyNotificationName = "kMyNotificationName"
}
class First {
init() {
NotificationCenter.default.addObserver(self, selector: #selector(self.notificationReceived), name: NotificationName.MyNotificationName, object: nil)
}
func notificationReceived() {
// Refresh table view here
}
}
class Second {
func postNotification() {
NotificationCenter.default.post(name: NotificationName.MyNotificationName, object: nil)
}
}
Once postNotification is called, the function notificationReceived in class First will be called.
Create a common global data store and let all the view controllers get their data from there. This is essentially a global singleton with some accompanying functions. I know you wanted to do this without global variables but I think you should consider this.
Create a class to contain the data. Also let it be able to reload the data.
class MyData {
static let shared = MyData()
var data : SomeDataType
func loadData() {
// Load the data
}
}
Register to receive the notification as follows:
static let dataChangedNotification = Notification.Name("DataChanged")
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
}
override func viewDidLoad() {
super.viewDidLoad()
// Establish a way for call activity to notify this class so it can update accordingly
NotificationCenter.default.addObserver(self, selector: #selector(handleDataChangedNotification(notification:)), name: "DataChanged", object: nil)
}
func handleDataChangedNotification(notification: NSNotification) {
// This ViewController was notified that data was changed
// Do something
}
func getDataToDisplay() {
let currentData = MyData.shared.data
// do something
}
// Any view controller would call this function if it changes the data
func sendDataChangeNotification() {
let obj = [String]() // make some obj to send. Pass whatever custom data you need to send
NotificationCenter.default.post(name: type(of: self).dataChangedNotification, object: obj)
}

how can I call a function in swift when the loop in other function completes running?

I'm currently writing a function in Swift to remove all annotations from the map.
I want to add a fade out effect when they are being removed, so I thought about the following argument:
Iterate over all annotations
for each annotation change its alpha to 0 with animations
when everything has completed - remove annotations from the map.
The code that I have so far changes already the alpha of each marker, but I don't know how to invoke the function responsible for removing markers when everything else is completed.
I have two functions:
func removeMarkersFromMap(){
self.array.removeAll()
let annotationsToRemove = mapView.annotations.filter { $0 !== mapView.userLocation }
for annotation in annotationsToRemove {
let pinView = mapView.view(for: annotation)
UIView.animate(withDuration: 2, delay: 0, options: UIViewAnimationOptions.curveEaseOut, animations: {
pinView?.alpha = 0.0
}, completion: { (finished: Bool) -> Void in
print("removed single pin")
})
}
}
and:
func removeCompletelyAnnotations(){
let annotationsToRemove = mapView.annotations.filter { $0 !== mapView.userLocation }
self.mapView.removeAnnotations( annotationsToRemove )
}
How can I call the 2nd function when the loop inside the 1st function is completed?
The trick I use for this kind of thing is to place the final completion code in the deInit method of an object instance that is captured by the in the individual completions.
For example :
class FinalCompletion
{
var codeBlock:()->()
init(_ code:#escaping ()->()) { codeBlock = code }
func waitForLast() {}
deinit { codeBlock() }
}
So in your calling code you can proceed as follows :
func removeMarkersFromMap()
{
...
// setup the final completion in a local variable
let removeAnnotation = FinalCompletion(removeCompletelyAnnotations)
for annotation in annotationsToRemove
{
let pinView = mapView.view(for: annotation)
UIView.animate(withDuration: 2, delay: 0, options: UIViewAnimationOptions.curveEaseOut, animations: {
pinView?.alpha = 0.0
}, completion: { (finished: Bool) -> Void in
print("removed single pin")
// reference the local variable to make it part of the capture
removeAnnotation.waitForLast()
})
}
}
The way it works is that the local variable (FinalCompletion object) will remain "alive" as long as at least one of the completion blocks is also alive. When the completion blocks are executed, they go out of scope and release their hold on the captured local variable. When the last completion block is executed and goes out of scope, the local variable no longer has any references to it so it also goes out of scope. This is when its deinit method is called. ( i.e. after all completion blocks have been executed).
Why not simply remove the annotation in the completion block rather than using a separate method
for annotation in annotationsToRemove {
let pinView = mapView.view(for: annotation)
UIView.animate(withDuration: 2, delay: 0, options: UIViewAnimationOptions.curveEaseOut, animations: {
pinView?.alpha = 0.0
}, completion: { (finished: Bool) -> Void in
self.mapView.removeAnnotation(annotation)
})
}
}

KVO not working (swift 2.2)?

In my app, OneVC is one of child ViewControllers of PageViewController, TwoVC is the embed view controller of OneVC's Container View.
When the user drag the scroll view in OneVC, I want the drag action can be not just update the content in OneVC from web API, but notify TwoVC to update too.
Both OneVC and TwoVC will appear at interface at the same time when launch.
I'm following Apple's "Using Swift with Cocoa and Objective-C" "Key-Value Observing" instruction to imply KVO, but no notification is sent when the observed property changes. Please see below my code:
OneVC is the object to be observed.
class OneVC: UIViewController, UIScrollViewDelegate {
dynamic var isDragToUpdate = false
func scrollViewDidEndDragging(scrollView: UIScrollView, willDecelerate decelerate: Bool) {
if scrollView.contentOffset.y < -150 {
if isDragToUpdate {
isDragToUpdate = false
} else {
isDragToUpdate = true
}
print(isDragToUpdate)
}
}
}
TwoVC is the observer
class TwoVC: UIViewController {
let oneVC = OneVC()
override viewDidLoad() {
oneVC.addObserver(self, forKeyPath: "isDragToUpdate", options: [], context: nil)
}
override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
print("hoh")
guard keyPath == "isDragToUpdate" else {return}
print("hah")
}
deinit {
oneVC.removeObserver(self, forKeyPath: "isDragToUpdate")
}
}
I checked row by row, and find many other stackoverflow answers, but still no idea what's going wrong on my code, when drag and release the scrollview, none of "hoh" and "hah" are print in console, except print(isDragToUpdate) is printed properly.
Thank you in advance!

Function Calling After ViewController Is Dismissed

I have a func that calls itself over and over again for a simple little slideshow.
func animateImage(no:Int)
{
println("ANIMATE")
var imgNumber:Int = no
let t:NSTimeInterval = 1;
let t1:NSTimeInterval = 0;
let url = NSURL(string: self.itemss[imgNumber][0])
let data = NSData(contentsOfURL: url!) //make sure your image in this url does exist, otherwise unwrap in a if let check
imgView!.alpha = 0.4
self.imgView?.image = UIImage(data: data!)
//code to animate bg with delay 2 and after completion it recursively calling animateImage method
UIView.animateWithDuration(2.0, delay: 0, options:UIViewAnimationOptions.CurveEaseOut, animations: {() in
self.imgView!.alpha = 1.0;
},
completion: {(Bool) in
imgNumber++;
if imgNumber>self.itemss.count-1 //only for 4 image
{
imgNumber = 0
}
self.animateImage(imgNumber);
})
}
The problem I'm having is that the func seems to continue to call and doesn't seem to be stopping when i dismiss the view controller ? Why is this func continuing to call after the view controller is dismissed.
#IBAction func dismissMe(sender: AnyObject) {
if((self.presentingViewController) != nil){
self.dismissViewControllerAnimated(false, completion: nil)
println("done")
}
}
The reason that your function is continuing to be called after the view controller is dismissed is because there's no logic that should prevent it from discontinuing to call itself once it is dismissed.
A dismissed view controller should cease to have a parent view controller, however, so we could put an end to this loop by checking if there's a parent view controller at the top of the method:
if self.parentViewController == nil {
return;
}
If this is the first thing your method does, then the method should only continue to execute (and line up another call to itself) if the view controller still has a parent.