Refreshing a view controller on Firebase logout - swift

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!

Related

Collection View is reloaded then why wrong item disappears

I was seeing a code where a collection view is made and we can mark some items as favorite. Also there is a filter button on top if we select that only favorite items will be visible.
Now is favorite filter is applied, then any random item is unfavourated then from the collection view, the last item disappears (which is wrong, but the bussiness logic going on is right I have tested multiple times using breakpoints) and now if I go back and come to favorites page again then the wrong item which disappeared is in the list and the correct item which was unfavorited is not present because the businesses logic was right and the data source has correct elements so the collection view made is correct (what i think).
Now I guess after tapping favorite button something wrong is hapenning, that the collection view reloading has some problem.
Also second question is is always mandatory to call
collectionView.reloadData()
and
collectionView.reloadItems(at: [IndexPath(item: index, section: 0)])
in main thread using DispatchQueue.main.async
I am new to swift, can you help out what could be the case that the reload is happening is wrong but in data source correct element is removed.
Problem gif: View Here
I was not able to add in this post
UPDATE: added code snippets:
On tapping heart symbol this function is invoked:
func editFavouriteChannelList(with viewModel: MyChannelViewModel?, index: Int) {
guard var viewModel = viewModel,
viewModel.isFavouriteEnabled,
var channel = viewModel.channels?[safe: index],
!channel.state.isChangingStateInProgress() else {
return
}
// Some working
// Calls updateCell fn with same viewModel but updated channel state
self.updateCell(with: viewModel, channelCellModel: channel)
// This makes API call then again we call on it's success updateCell fn with viewModel have removed that updated channel
interactor.editFavouriteChannelList(with: channel, isMarkingFavourite: isMarkingFavourite)
}
This presenter's updateCell is called twice,
first when only channelCellModel is updated
and second time after making an API call that time viewModel will have that channel removed from it.
In presenter:
private func updateCell(with viewModel: MyChannelViewModel, channelCellModel: MyChannelCellModel) {
DispatchQueue.main.async { [weak self] in
self.interface?.updateCell(with: viewModel, channelCellModel: channelCellModel)
}
}
In ViewController:
func updateCell(with viewModel: MyChannelViewModel, channelCellModel: MyChannelCellModel) {
if myChannelVM?.channels?.count != viewModel.channels?.count {
self.myChannelVM = viewModel
collectionView.reloadData()
} else {
guard let index = myChannelVM?.channels?.firstIndex(of: channelCellModel) else {
return
}
myChannelVM?.channels?[index] = channelCellModel
print(" Like: updateCell local : \(myChannelVM?.channels?[index])")
collectionView.reloadItems(at: [indexPath])
}
}
In View Controller logic going on is like this first call to updateCell is made where viewModel is same as myChannelVM but channelCellModel is a channel with updated state.
Then second time call to updateCell is made where viewModel is same as myChannelVM but not having that channel which was earlier marked unfavorite.
Now if I remove collectionView.reloadItems(at: [indexPath]) from else block and use collectionView.reloadData() in place of it then collection view updates correctly otherwise not.
So updateCell(at:) is interfereing with reloadData() of when made in next call.
So there is issue like Why is a call to reloadItems(at:) stopping/breaking call to reloadData() in Collection View?
What is the reason for this in my case?

PHPickerViewController hide Search Bar and Navigation Bar

I've been trying to implement a photo selection feature in a new app. My current approach is to use a PHPickerViewController embedded in a UIViewControllerRepresentable to display in a swiftUI view.
This is my makeUIViewController function.
func makeUIViewController(context: Context) -> PHPickerViewController {
var configuration = PHPickerConfiguration()
configuration.filter = filter
configuration.selectionLimit = limit
let controller = PHPickerViewController(configuration: configuration)
controller.delegate = context.coordinator
return controller
}
It is inside a struct named PhotoPicker :
struct PhotoPicker: UIViewControllerRepresentable {
What I want to hide is this part :
Yes, all of that.
Let me explain myself, the PickerView is always presented, it is not a pop-up, so there is no need for a cancel button. As you can see there is no done button either. That's because only one image needs to be selected so what happens is when the user taps on an image, the event that a new image was selected is called immediately. Removing the need for user confirmation. Then concerning the search bar, I don't really want it, I just want the user to select a photo and finally the little switch between photos and albums isn't really necessary in my case either.
I've tried a lot of different ways, including trying to set options for the controller when it is created in makeUIViewController. These options were for example :
controller.navigationController?.setToolbarHidden(true, animated: false)
controller.navigationController?.setNavigationBarHidden(true, animated: false)
And I also tried invoking view modifier in my SwiftUI body :
PhotoPicker(filter: .images, limit: 1) { (results) in
}
.navigationBarTitle("")
.navigationBarHidden(true)
.statusBar(hidden: true)
.navigationBarBackButtonHidden(true)
But again, none of them seems to work. So that's why I'm asking here, because it seems I tried everything and nothing is working...

How to test if the app is presenting a certain view controller?

I'm pretty new to XCode UI tests and I'm trying to run a test where I fill two text labels and then I press a button. After the button is pressed the app should make an URL call and be redirected to another view controller. I want to check if at the end of this operation the second view controller is displayed.
To test this, I have written the following test:
let app = XCUIApplication()
app.launch()
let ownerTextField = app.textFields["ownerTextField"]
ownerTextField.tap()
ownerTextField.typeText("UserA")
let repositoryTextField = app.textFields["repositoryTextField"]
repositoryTextField.tap()
repositoryTextField.typeText("AppB")
app.buttons["SearchButton"].tap()
XCTAssertTrue(app.isDisplayingResults)
Where isDisplayingResults is
extension XCUIApplication {
var isDisplayingResults: Bool {
return otherElements["resultView"].exists
}
}
I have set up the identifier of the View controller inside its swift file class:
override func viewDidLoad() {
super.viewDidLoad()
view.accessibilityIdentifier = "resultView"
...
Nonetheless to say, the test fails. How can I get a success?
It's so simple.
If after clicking the URL, Viewcontroller is presenting means your previous VC button doesn't exist on screen.
So Just check for previous VC button exists or not.
If app.buttons["SearchButton"].esists()
{ //write if code
} else {
// Write else code
}

How to check if UIViewController is already being displayed?

I'm working on an app that displays a today extension with some information. When I tap on the today extension, it opens the app and navigates to a subview from the root to display the information. Normally the user would then click the back arrow to go back to the main view, but there is no way to tell if this is actually done. It is possible for the user to go back to the today extension and tap again. When this is done, the subview is opened once again with new information. If this is done a bunch of times, I end up with a bunch of instances of the subview and I have to click the back button on each of them to get back to the main view.
My question: Is it possible to check if the subview is already visible? I'd like to be able to just send updated information to it, instead of having to display an entirely new view.
I am currently handling this by keeping the instance of the UIViewController at the top of my root. If it is not nil, then I just pass the information to it and redraw. If it is nil, then I call performSegue and create a new one.
I just think that there must be a better way of handling this.
Edit: Thanks to the commenter below, I came up with this code that seems to do what I need.
if let quoteView = self.navigationController?.topViewController as? ShowQuoteVC {
quoteView.updateQuoteInformation(usingQuote: QuoteService.instance.getQuote(byQuoteNumber: quote))
}
else {
performSegue(withIdentifier: "showQuote", sender: quote)
}
This is different from the suggested post where the answer is:
if (self.navigationController.topViewController == self) {
//the view is currently displayed
}
In this case, it didn't work because I when I come in to the app from the Today Extension, it goes to the root view controller. I needed to check whether a subview is being displayed, and self.navigationController.topViewcontroller == self will never work because I am not checking to see if the top view controller is the root view controller. The suggestions in this post are more applicable to what I am trying to accomplish.
u can use this extension to check for currently displayed through the UIApplication UIViewController:
extension UIApplication {
class func topViewController(base: UIViewController? = UIApplication.shared.keyWindow?.rootViewController) -> UIViewController? {
if let nav = base as? UINavigationController {
return topViewController(base: nav.visibleViewController)
}
if let tab = base as? UITabBarController {
if let selected = tab.selectedViewController {
return topViewController(base: selected)
}
}
if let presented = base?.presentedViewController {
return topViewController(base: presented)
}
return base
}
}
and usage example:
if let topController = UIApplication.topViewController() {
if !topController.isKind(of: MainViewController.self) { //MainViewController- the controller u wish to equal its type
// do action...
}
}

Reload regular view controller (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()
}