Reload regular view controller (swift) - swift

My view controller display the view depends on an array. the object of the array may be removed according to user interaction. So i have to reload the view controller once the user delete data in the array. is there any way to reload the regular view controller?

You should design the view according to the array in one method and call it every time the array changes.
private func setupView() {
...
self.setNeedsLayout()
}
private func deleteObject() {
myArray.removeAtIndex(2)
self.setupView()
}

Related

Using NSPageController in History Mode (Content Mode) and no View Controllers?

I'm building an app with three views and I want the user to be able to swipe between them but also navigate between them by using custom buttons, like browsing in safari but only with three available views. The user can navigate between them in a somewhat random order and I provide back and forward buttons in a toolbar.
After reading Apple's documentation I believe history mode is the option I'm looking for as I can provide the views separately and navigation is not predefined like in book mode, however when I implement it it adds my view to the arrangedObjects property but the view itself doesn't change. I've correctly wired the page controller view programmatically to a subview of the app's contentView and I can see said view correctly displayed while debugging the view hierarchy.
The "Page Controller Managed View" is set as NSPageController.view programatically.
When I try using navigateForward(to:) the view gets added correctly to the arrangedObjects and the selectedIndex is correctly set but the NSPageController.view doesn't change at all (remains a blank NSView like the loaded view from storyboard). This is my code:
class MainViewController: NSViewController {
// MARK - Instance Properties
var notificationObservers: Array<NSObjectProtocol> = []
var pageController: NSPageController?
// MARK - Interface Builder Outlets
#IBOutlet var mainContentView: NSView? // Named "Page Controller Managed View" in storyboard
override func viewDidLoad() {
super.viewDidLoad()
pageController = (NSStoryboard.main!.instantiateController(
withIdentifier: "MainPageController") as! NSPageController)
pageController!.delegate = AppDelegate
pageController!.view = mainContentView!
notificationObservers.append(
NotificationCenter.default.addObserver(
forName: NSApplication.didFinishLaunchingNotification,
object: NSApp,
queue: .main,
using: { [weak self] (notification) in
// If the 'arrangedObjects' property is 0 it means no other object has set the first view
if self?.pageController!.arrangedObjects.count == 0 {
// Set the first view
self.pageController!.navigateForward(to: AppDelegate.firstView.nsView)
}
})
)
}
deinit {
// Perform cleanup of any Apple Notification Center notifications to which we may have registered
notificationObservers.forEach { (observer) in
NotificationCenter.default.removeObserver(observer)
}
}
}
This is the view hierarchy while running the app:
What am I doing wrong? Why is the page controller view not being updated with the navigateForward(to:) view?
This could shorten the amount of code I have to use by a lot compared to using view controllers (Book mode) so any help is appreciated.

Swiping back to dismiss a child VC while passing data

I have a Navigation Controller that contains 1 Parent VC and 1 Child VC. Data is passed between the two. On the Parent VC, I am passing data to the Child VC via prepareForSegue. On the Child VC back to Parent VC, I am passing data via a custom back button unwind segue.
Passing the data back and forth work great. However, I would like the Child VC to be able to be swiped right to close, while still passing the same information as in the unwind segue.
Is there a way to pass the data back to the parent using a swipe, along with being able to press the back button unwind? The only gesture method I am using is gestureRecognizerShouldBegin, which allows the Child VC to be swiped to the right.
extension ChildVC: UIGestureRecognizerDelegate {
func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
}
For reference, here's my unwind:
#IBAction func unwindfromChild(_ sender: UIStoryboardSegue) {
if let CVC = sender.source as? ChildVC {
print("Unwind")
dataReceived() // do stuff with the received data
}
}
You can simply attach a swipeGestureRecognizer to your view, name the unwind segue in Interface Builder, and inside the target function trigger the unwind segue.
I solved this with a slightly different approach. I fully removed the unwind segue. Passing the data through delegation. The delegate method is called in navigationController:willShow.

UICollectionView's prefetchItemsAt not being called

I'm trying to implement data prefetching for my UICollectionView using the UICollectionViewDataSourcePrefetching protocol; however, the respective method is not being called.
The collection view has a custom layout. I have also tried with the regular flow layout, same results.
Upon data reload, I execute the following code to make the collection view have the size of its content to prevent scrolling within that collection view but in a scroll view outside the collection view:
func reloadData() {
viewDidLayoutSubviews()
collectionView.reloadData()
collectionView.layoutIfNeeded()
viewDidLayoutSubviews()
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
collectionViewHeightConstraint.constant = collectionView.collectionViewLayout.collectionViewContentSize.height
collectionView.layoutIfNeeded()
view.layoutIfNeeded()
}
Maybe that has something to do with it?
What I have done:
my UIViewController does inherit from UICollectionViewDataSourcePrefetching
collectionView.prefetchDataSource = self (also tried using storyboard)
collectionView.isPrefetchingEnabled = true (also tried using storyboard)
I have implemented collectionView(_:prefetchItemsAt:)
Issue:
The prefetchItemsAt method is not being called. I determined that by placing a print statement in the method & setting up a breakpoint.
Like requested in the comments, I'll share my implementation for this issue here:
I created the tracker prefetchState which determines whether I'm prefetching at the moment, or not:
enum PrefetchState {
case fetching
case idle
}
var prefetchState: PrefetchState = .idle
Then, I hooked up the scroll view's delegate (the scroll view my collection view is in) to my view controller and implemented the scrollViewDidScroll method:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
guard scrollView == self.scrollView else { return }
let prefetchThreshold: CGFloat = 100 // prefetching will start 100pts above the bottom of the scroll view
if scrollView.contentOffset.y > scrollView.contentSize.height-screenBounds.height-prefetchThreshold {
if prefetchState == .idle {
prefetchItems()
}
}
}
In there, you can see that I check whether we're already prefetching. If not, I call prefetchItems(), as implemented here:
func prefetchItems() {
guard prefetchState == .idle else { return }
prefetchState = .fetching
someDownloadFuncWithCompletionBlock { (newItems) in
self.dataSource += newItems
self.collectionView.reloadData()
self.prefetchState = .idle
}
}
I execute the following code to make the collection view have the size of its content to prevent scrolling within that collection view but in a scroll view outside the collection view:
This sounds very broken.
Why are you doing this?
Prefetching on the collection view (from the docs) is triggered when the user scrolls the collection view. By making the collection view frame the same as the content size you are essentially disabling the scrolling of the collection view itself.
The collection view calls this method as the user scrolls
If you are forcing the collection view frame to be the same as the content size then you are entirely breaking UICollectionView.
The reason the prefetch isn't called is because every cell has been loaded already. Nothing is in prefetch any more. Because your collection view is displaying every cell at the same time.
If you want to scroll a collection view... let the collection view handle it. You shouldn't need to place the collection view inside another scroll view.

Refreshing a view controller on Firebase logout

I have a view controller that has a UICollectionView with information pulled from Firebase. When a user visits this view controller their info is placed in the collection view cells.
However, when the user logs out and logs in with a different account, the previous user's data stays in the collection view rather than re-populating with the new user's info.
Right now everything is called from viewDidLoad():
override func viewDidLoad() {
super.viewDidLoad()
loadData()
loadOngoingQuest()
collectionView.dataSource = self
collectionView.delegate = self
}
loadData() and loadOngoingQuest() pull info from Firebase into arrays used to populate the collection view.
I have tried adding everything in viewWillAppear() instead, but it causes some glitches as the collection view involves scrolling and other animations that I don't want to run every time the view controller appears.
Basically I just want to reload the entire view controller every time the user logs out. How can I do this?
You should do something like this, see comments for more info:
let firebaseAuth = FIRAuth.auth()
do {
try firebaseAuth?.signOut()
// clear the array that your collectionView is based on
// reload your collectionView (which will clear all the data since your array is empty)
} catch let signOutError as NSError {
print ("Error signing out: %#", signOutError)
}
Update:
Another way of handling this:
Create a Struct with the following property:
struct Global {
var isNewUser = false
}
Whenever you logout a user do this:
Global.isNewUser = true
And in your collectionView you check if Global.isNewUser == true then reload everything and most important do set Global.isNewUser = false. That way you´ll only load everything once.
After a lot of frustration I figured out the issue!
It was nothing to do with refreshing collection views or view controllers. I was using Firebase refs wrong. I had saved two refs in as global constants in a struct in hopes of not having to declare them on every VC. It was like this:
struct GlobalConstants {
struct Refs {
static let userRef = AppDatabaseReference.users(uid: (Auth.auth().currentUser?.uid)!).reference()
static let currentUser = Auth.auth().currentUser?.uid
}
}
I would then use them anywhere in my code like this:
var questsRef = var questsRef = GlobalConstants.Refs.userRef.reference().child("quests")
This would cause issues unless I terminated the app and opened it again, refreshing these variables.
I went back to declaring let userRef = AppDatabaseReference.users(uid: (Auth.auth().currentUser?.uid)!).reference() at the top in every VC and it solved the issue!

What is the best way to reload a child tableview contained within a parent VC that is being fed fresh data?

I have been playing with the concept of the parent/child view delegation for a few days now, and currently understand how to feed data from parent to child. However, now, I want a button in the parent (main VC) to reload the data presented in the child VC.
I'm trying to delegate a method that is activated in the child VC's class but is activated in the parent's navigation controller. So that when I press the button, the delegated method in the child VC is performed; in my case, that method would be reload table. Why am I getting so many errors when trying to set up this simple delegation relationship?
My parent/container View is currently delegating a method to the child, so I have it set up from child -> parent. But I want to set it up from parent -> child. Pretty much I have:
struct Constants {
static let embedSegue = "containerToCollectionView"
}
class ContainerViewController: UIViewController, CollectionViewControllerDelegate {
func giveMeData(collectionViewController: CollectionViewController) {
println("This data will be passed")
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == Constants.embedSegue {
let childViewController = segue.destinationViewController as! CollectionViewController
childViewController.delegate = self
}
}
FROM CHILD:
protocol CollectionViewControllerDelegate {
func giveMeData(collectionViewController: CollectionViewController)
}
class CollectionViewController: UIViewController {
var delegate:CollectionViewControllerDelegate?
override func viewDidLoad() {
super.viewDidLoad()
self.delegate?.giveMeData(self)
// Do any additional setup after loading the view.
}
I think my trouble is the fact that I'm declaring the child delegate in a prepareforsegue, so that was straight forward, but now I want the reverse delegation. How do I set that up so that I can use a child-method from the parent VC?
The child view controller has no business supplying other controllers with data. It should actually not even have any data fetching logic that is so generic it is also used by other controllers. You should refactor the data methods out into a new class.
This pattern is called Model-View-Controller, or MVC, and is a very basic concept that you should understand and follow. Apple explains it pretty well.
In general, to send data to from a controller to a detail controller, use prepareForSegue to set properties, etc. To communicate back to the parent controller, you use delegate protocols, but usually these are called when the detail controller is finished with its work and just reports the result up to the parent.
If you want to update the detail VC with new data (without dismissing it and with the parent not visible) you should not put the logic to update it into the parent. Instead, use the structure suggested above.