My current program is about mostly TableViews and CollectionViews that fetches data from an API, The problem is that the data is fetched only when the user goes to the current tab which the TableView is present and the data fetches from the moment the user went to the tab and needs to wait till the data come because when user logs-in a token is shared with other classes and used later to fill them.
I don't know how can I make the data filled automatically after the user is logged-in not when the user goes to the specific tab and wait for them to load, Basically make a request when the user loggs-in so he doesn't have to go to the tabs and wait 5+ seconds
signInViewController:
#IBAction func signInSegueToDashboard(_ sender: Any) {
activityLoaderSignIn.startAnimating()
APICallerPOST.shared.signInToAccount(username: emailFieldSignIn.text!, password: passwordFieldSignIn.text!) { [self] (result, error) in
if result?.StatusCode != 0 {
DispatchQueue.main.async {
activityLoaderSignIn.stopAnimating()
}
}
switch result?.StatusCode {
case 0:
APICallerGET.shared.token = result?.Result.token
assignDataFromSignIn.DefaultsKeys.keyOne = (result?.Result.completeName)!
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
guard let mainTabBarController = self.storyboard?.instantiateViewController(withIdentifier: "mainTabBarController")
else {
return
}
mainTabBarController.modalPresentationStyle = .custom
self.present(mainTabBarController, animated: true, completion: nil)
}
case 1:
DispatchQueue.main.async {
}
case 2:
DispatchQueue.main.async {
}
default:
break
}
}
}
invoicesViewController:
// the function which is used at viewDidLoad at a TableView class
func fetchAndReloadData(){
APICallerGET.shared.propertiesOfAccount(for: APICallerGET.shared.token!) { [self] (result, error) in
switch result?.StatusCode {
case 0:
self.notificationsNew = result!
self.notifications = self.notificationsNew
DispatchQueue.main.async {
self.invoicesTableView.reloadData()
activityLoaderInvoices.stopAnimating()
}
case 1:
print("error")
default:
break
}
}
}
Now if there is a way to load this function of the invoicesViewController straight when the user signs-in so he doesn't have to wait till the data loads when he went to the tab with the TableView.
Related
I am using PHPicker for the first time, using loadFileRepresentation method, which asynchronously writes a copy of a selected file and returns a progress object. I am attaching this progress object to a parent progress where I track progress of copying of all files selected by user, so that I can drive one single progress bar view.
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
picker.dismiss(animated: true)
var tasks = [Progress]()
let parentTask = Progress()
DispatchQueue.global(qos: .unspecified).async {
for itemProvider in results.map({ $0.itemProvider }) {
if itemProvider.canLoadObject(ofClass: UIImage.self) {
guard let identifier = itemProvider.registeredTypeIdentifiers.first else { return }
guard let filenameExtension = URL(string: identifier)?.pathExtension else { return }
let newTask = itemProvider.loadFileRepresentation(forTypeIdentifier: UTType.image.identifier) { tempPathForFileCopying, error in
if (error != nil) {
print("Error while copying files \(String(describing: error))")
}
let targetPath = self.viewModel.galleryManager.selectedGalleryPath.appendingPathComponent(UUID().uuidString).appendingPathExtension(filenameExtension)
if let tempPathForFileCopying {
do {
try FileManager.default.copyItem(at: tempPathForFileCopying, to: targetPath)
} catch {
print("Error \(error)")
}
self.viewModel.galleryManager.buildThumb(forImage: AlbumImage(fileName: targetPath.lastPathComponent, date: Date()))
self.imagesToBeAdded.append(AlbumImage(fileName: targetPath.lastPathComponent, date: Date()))
}
}
tasks.append(newTask)
parentTask.addChild(newTask, withPendingUnitCount: newTask.totalUnitCount - newTask.completedUnitCount)
}
}
}
for task in tasks {
parentTask.totalUnitCount += task.totalUnitCount
}
self.screenView.progressView.observedProgress = parentTask
self.showLoading(task: parentTask)
}
I than invoke showLoading function, which should end up in while loop, until task.isFinished is true. Strangely, printing progress into the console works fine, and I can see that progress there, but no matter what, I cannot update UI progress bar view from here. Entire UI is stuck, until all the copies of the files are created. Even though that happens on background thread and I am trying to call progressView.setProgress on main thread. Again, printing current progress into the console works fine, but no matter what, UI doesnt update until all that copying is done, which just by that time is useless
func showLoading(task: Progress) {
DispatchQueue.main.async {
while !task.isFinished {
var oldFraction = task.fractionCompleted
usleep(300)
if task.fractionCompleted != oldFraction {
let progress = Float(task.fractionCompleted)
self.screenView.progressView.setProgress(progress, animated: false)
print(progress)
}
}
self.viewModel.addPhotos(images: self.imagesToBeAdded)
}
}
I need to detect when the bottom In-App Purchase pop-up opens up. In my current development, I show an activity indicator just when a user taps on the "Purchase Button" for a particular product bundle. And the indicator keeps loading until the whole purchase process finishes.
But I want to stop the activity indicator when the bottom purchase pop-up alert will be shown and the indicator will be spinning again when the user fills up all credentials and hit the subscription button on that bottom pop-up. How could I detect that?
In my current implementation when a user tap on the purchase button I start the activity indicator at the beginning like this:
func purchaseProduct(product: SKProduct) -> Bool {
if !IAPManager.shared.canMakePayments() {
return false
} else {
if Reachability.isConnectedToNetwork() {
setActivityIndicator(shouldStart: true)
UserDefaults.standard.set(true, forKey: "receiptValidationAllow")
IAPManager.shared.purchaseProduct(product: product) { (result) in
DispatchQueue.main.async {
switch result {
case .success(let isPurchasedActive):
if isPurchasedActive == true {
self.navigateToHomeViewController()
} else {
GlobalMethod.appdelegate().navigateToInitialViewController()
}
// self.purchasedProductSuccessfully(product: product)
print("finally purchased product \(product.localizedTitle)")
case .failure(let error):
self.showIAPRelatedError(error)
}
self.setActivityIndicator(shouldStart: false)
}
}
} else {
showNoInternetAlert()
}
}
return true
}
This action calls the below function in my IAPManager class:
func purchaseProduct(product: SKProduct, withHandler handler: #escaping ((_ result: Result<Bool, Error>) -> Void)) {
let payment = SKPayment(product: product)
SKPaymentQueue.default().add(payment)
// Keep the completion handler.
onBuyProductHandler = handler
}
Here is the bottom pop-up that arrives on the screen. But I need to detect this pop-up event. How can I detect that?
In the updatedTransactions delegate all the cases trigger after finishing the transactions. So I can't get the pop-up trigger inside of it.
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
transactions.forEach { (transaction) in
switch transaction.transactionState {
case .purchased:
SKPaymentQueue.default().finishTransaction(transaction)
case .restored:
totalRestoredPurchases += 1
SKPaymentQueue.default().finishTransaction(transaction)
case .failed:
totalPurchaseOrRestoreFailed += 1
SKPaymentQueue.default().finishTransaction(transaction)
case .deferred, .purchasing: break
#unknown default: break
}
}
}
Finally, I call the receipt validation inside of the removedTransactions delegate, get the handler call back and stop the activity indicator inside of the button.
func paymentQueue(_ queue: SKPaymentQueue, removedTransactions transactions: [SKPaymentTransaction]) {
print("Removed transactions: \(transactions.count)")
print("Unfinished transaction: \(queue.transactions.count)")
//This will be called after finishing all transactions
if queue.transactions.count == 0 {
if totalPurchaseOrRestoreFailed != 0 {
transactions.forEach { (transaction) in
switch transaction.transactionState {
case .purchased:break
case .restored: break
case .failed:
if let error = transaction.error as? SKError {
if error.code != .paymentCancelled {
onBuyProductHandler?(.failure(error))
} else {
onBuyProductHandler?(.failure(IAPManagerError.paymentWasCancelled))
}
print("IAP Error:", error.localizedDescription)
totalPurchaseOrRestoreFailed = 0
}
case .deferred, .purchasing: break
#unknown default: break
}
}
} else {
self.IAPResponseCheck(iapReceiptValidationFrom: .purchaseAndRestoreButton)
UserDefaults.standard.set(false, forKey: "receiptValidationAllow")
}
}
}
I'm downloading some JSON from an API, but there is another prerequisite (an animation) that must be complete before we move to the next screen.
I've decided to use DispatchGroup to do this, but also need to have a reload as well.
I've come up with a solution that uses a completion handler that is either dispatchGroup.leave or simply moving to the next screen, since we only need to download the data once.
let dispatchGroup = DispatchGroup()
var userName: String?
func fetchData() {
dispatchGroup.enter()
dispatchGroup.enter()
resolveDataRequirements(dispatchGroup.leave)
dispatchGroup.notify(queue: .main) { [weak self] in
self?.moveToNextScreen()
}
}
func resolveDataRequirements(_ completion: #escaping(() -> Void)) {
api.resolve { [weak self] result in
switch result {
case .success(let user):
self?.userName = user
completion()
case .failure:
break
}
}
}
func moveToNextScreen() {
// move to next screen, but passes the user
}
// reload data, and once the data is downloaded move to next screen
func reloadData() {
resolveDataRequirements(moveToNextScreen)
}
// the user might choose to fetch data
fetchData()
// now if the user wanted to reload
reloadData()
Now the problem is that this is in a view model - so the user String is effectively a state that I wish to eradicate.
Is there any way I can pass the user String to dispatchGroup.notify or similar?
You can use inout properties and change userName scope like this:
func fetchData() {
var userName = ""
dispatchGroup.enter()
dispatchGroup.enter()
resolveDataRequirements(dispatchGroup.leave, &userName)
dispatchGroup.notify(queue: .main) { [weak self] in
self?.moveToNextScreen(userName)
}
}
func resolveDataRequirements(_ completion: #escaping(() -> Void), _ userName: inout String) {
api.resolve { [weak self] result in
switch result {
case .success(let user):
userName = user
completion()
case .failure:
break
}
}
}
func moveToNextScreen(_ userName: String) {
print(userName)
}
I just don't understand why you are calling enter() twice and leave() just once
I have a reference to a project image in Firebase, and I want to edit the photo by replacing the data in the storage reference, without changing the reference link. I am successfully doing this, and the image view is updating as well when a user selects a new image. However, when I go back in the navigation controller, my tableView still shows the same image - and when I click on the tableView item - the project image does not seem to be updated.
The only way the image and tableview data successfully update is when I delete the simulator from my computer and re-add it. Here is some of my code for how I populate the tableView - the code works great for new projects being added but the modified portion is not working - I call this function in viewwillappear to populate the tableview:
func checkForUpdates(uid: String) {
handler = db.collection("Projects").whereField("UID", isEqualTo: "\(uid)").addSnapshotListener { snapShot, error in
guard let document = snapShot else {return}
if document.count > 0 {
document.documentChanges.forEach { difference in
if difference.type == .added {
if let project = Project(dictionary: difference.document.data()) {
self.projectArray.append(project)
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
else {return}
}
if difference.type == .modified {
DispatchQueue.main.async {
self.loadData(uid: uid)
self.tableView.reloadData()
}
return
}
if difference.type == .removed {return}
else {return}
}
}
else {
print("no documents to show")
}
}
}
When I segue into viewing a particular project (by clicking on tableviewcell) - here is my code
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "viewProject" {
if let vc = segue.destination as? ViewMyProject {
vc.selectedProject = self.selectedProject!
}
else {return}
}
else {return}
}
Here is my code for how I set cells within a xib file
func setProject(project: Project) {
projectName.text = project.title
projectCategory.text = project.category
projectDate.text = project.timeStamp.dateValue().dateToString(style: .short)
projectImage!.sd_setImage(with: URL.init(string: project.imageLink)) { (image, error, cacheType, url) in
if error != nil {
print ("Error setting project cell image")
return
}
else {
print("Successfuly set project cell image")
}
}
}
And here is how I load the project after clicking on the cell (I call this in viewWillAppear)
func loadProject(project: Project) {
filterProjectFeedback(project: project)
if projectImage.image == nil {projectImage.sd_setImage(with: URL.init(string: project.imageLink))}
projectDescription.text = project.description
}
Thanks so much in advance - I have a feeling it has to do with a strong reference - but I cannot make my "projectArray" variable weak
Here is a code in swift 3 to refresh cache everytime:
imgCardBack.sd_setImage(with: URL(string: objUserData.back_image!), placeholderImage:UIImage(named: "cardBack"), options: .refreshCached)
How to update image in cache when image changed on server with SDWebImage
I am writing the iOS application using swift 4.2. I am making a service call to logout user.
I need to know where to use main thread (DispatchQueue.main.async).
Here is my code:
private func handleLogoutCellTap() {
logoutUseCase?.logout() { [weak self] (result) in
guard let self = self else { return }
switch result {
case let (.success(didLogout)):
didLogout ? self.handleSuccessfullLogout() : self.handleLogoutError(with: nil)
case let (.failure(error)):
self.handleLogoutError(with: error)
}
}
}
logoutUseCase?.logout() makes a service call and returns #escaping completion. Should I use DispatchQueue.main.async on this whole handleLogoutCellTap() function or just in a handling segment?
Move the control to main thread wherever you're updating the UI after receiving the response of logout.
If handleSuccessfullLogout() and handleLogoutError(with:) methods perform any UI operation, you can embed the whole switch statement in DispatchQueue.main.async, i,e.
private func handleLogoutCellTap() {
logoutUseCase?.logout() { [weak self] (result) in
guard let self = self else { return }
DispatchQueue.main.async { //here.....
switch result {
//rest of the code....
}
}
}
}