SVProgressHUD isn't showing when using setContainerView - ios

I'm having an issue with the setContainerView, the progress is not showing at all when I assign it to a custom view. I've tried everything with my UIView which is perfectly fine, but the progress remains invisible.
I've used it for a while now and had no problems without custom container views.
Any ideas ?
EDIT
For exemple, this is working (progress showing):
func download() {
SVProgressHUD.show()
User.downloadAllUsers { (user) in
//Doing things
DispatchQueue.main.async {
//Updating UI
SVProgressHUD.dismiss()
}
}
}
But this is not working (progress is not showing)
func download() {
SVProgressHUD.setContainerView(loadingContainerView)
SVProgressHUD.show()
User.downloadAllUsers { (user) in
//Doing things
DispatchQueue.main.async {
//Updating UI
SVProgressHUD.dismiss()
}
}
}

Purely FWIW, in your sample code, I think you're dismissing at the end of each operation rather than at the end of all operations ... I believe.
You'd probably use there a DispatchGroup as the solution.
A quick tutorial on using DispatchGroup would be beyond the scope of this QA.

Related

iOS Swift -> Dismiss a View with ongoing Function

Sup guys!
I would like to dismiss the view and run the Firestore task in the background ... without blocking the UI or displaying a loading animation.
The code works like this .. but is there a better method or is it fine like that?
self.dismiss(animated: true) {
DispatchQueue.global().async {
uploadImageFIR(image: image) { url in
//FIRESTORE TASK HERE
}
}

Swift - How to properly get data from REST API to Label in ViewController

I am trying to create an iOS app to get data from API that I want to show the user in a Label.
So far I have this:
func getJoke(completion: #escaping (ChuckNorrisResponse) -> ()) {
URLSession.shared.dataTask(with: URL(string: url)!) { data, response, error in
guard let data = data, error == nil else {
print("Something fucked up")
return
}
var result: ChuckNorrisResponse?
do {
result = try JSONDecoder().decode(ChuckNorrisResponse.self, from: data)
} catch {
print("Fucked up to convert \(error.localizedDescription)")
}
guard let joke = result else {
return
}
completion(joke)
}.resume()
}
And in the ViewController
func setNewJoke() {
jokesProvier.getJoke { joke in
self.JokeLabel.text = joke.value
}
But it doesn't like that I try to edit the text in the Label inside the closure.
It shows error - UILabel.Text must be used from main thread only.
I cannot find anywhere how should I do this properly so it works.
Thanks in advance
Basically, as Aaron stated - you have to pass the closure to the main thread with DispatchQueue.main.async. The reason is that URLSession.shared.dataTask completionHandler runs on the thread different from main and self.JokeLabel.text = joke.value is an UI update - you're changing the text on the label, and UIKit requires you to update the screen on the main thread! That's why you have to pass label update to the main thread. Trying to update it on the thread different from main will result in undefined behaviour - it may work, it may freeze, it may crash.So, whenever you're doing something on the background thread and at some point want to update the UI always pass this job to the main thread Hope this helps
I'd recommend calling the completion handler on the main thread. So instead of
completion(joke)
do
DispatchQueue.main.async { completion(joke) }

How to use 2 main threads without lags?

In my app, I am using WebSockets and Video streaming.
Playing the live stream on the back view(full screen) and displaying UIViews above it.
I need very reliably to display both of the views(video and UIView block).
When I wrap addSubview into DispatchQueue.main.async {}, my video lags on the very moment when the UIView block appears on the screen.
So based on that, can I somehow separate them into 2 different main threads, so they BOTH can be reliably be displayed on the screen without lagging?
The lag is likely an indication the UI operation happens in the next runloop even if it wasn't required in the 1st place. What I recommend is following extension:
extension DispatchQueue {
static public func asyncMainIfNeeded(work: #escaping () -> Void) {
if Thread.isMainThread {
work()
return
}
DispatchQueue.main.async {
work()
}
}
}
In comparison to DispatchQueue.main.async it will additionally perform a check whether an async main thread dispatch is required at all. Now instead of:
DispatchQueue.main.async {
//Your UI operation
}
you would do:
DispatchQueue.asyncMainIfNeeded {
//Your UI operation
}

Do I need DispatchQueue.main to update UI after an Alamofire request?

I'm following a tutorial about working with REST/web requests. In the tutorial, we're working towards a Pokedex app where we fetch Pokemon details from an API using Alamofire, and then displaying that data in our UIs.
Here's the related code:
typealias DownloadComplete = (Bool) -> ()
// Model class
func downloadPokemonDetails(completed: #escaping DownloadComplete)
{
Alamofire.request(_pokemonURL).responseJSON { (response) in
var success = true
if let jsonData = response.result.value as? Dictionary<String, Any>
{
// parse the json here
...
}
else
{
success = false
}
completed(success)
}
}
// Controller class
override func viewDidLoad() {
super.viewDidLoad()
pokemon.downloadPokemonDetails(completed: { (success) in
if success
{
self.updateUI()
}
else
{
print("FAILED: TO PARSE JSON DATA")
}
})
}
func updateUI()
{
attackLbl.text = pokemon.attack
defenseLbl.text = pokemon.defense
heightLbl.text = pokemon.height
weightLbl.text = pokemon.weight
}
Now my question is: shouldn't we use DispatchQueue.main. and update the UI there like so?
pokemon.downloadPokemonDetails(completed: { (success) in
if success
{
DispatchQueue.main.async {
self.updateUI()
}
}
The tutorial left it out and I'm not sure if DispatchQueue is needed here to update the UI. I know that updating UI in a background thread is bad practice so if anyone can shed some light on whether using DispatchQueue here to get the main thread is necessary or not, I would very much appreciate it.
If one does not want to read the whole comments section, I am posting here it as answer.
Firstly, read Alamofire docs, which clearly states: "Response handlers by default are executed on the main dispatch queue."
It means, you can call any UI related code in response block. If you still feel uncomfortable to rely on 3rd party lib doc, you can check by executing this swift3 snippet:
if Thread.isMainThread {
print("Main Thread")
}
xcode 9
Starting from xcode 9 there is a built-in Main Thread Checker which detects invalid use of AppKit, UIKit, and other APIs from a background thread. Main Thread Checker is automatically enabled when you run your app with the Xcode debugger.
If any part of your project contains invalid UI calls from background thread, you will see the following:
** Demonstrated in Xcode Version 9.1 (9B55)

Setting a subview.hidden = false locks up UI for many seconds

I'm using a button to populate a UIPickerView on a hidden UIVisualEffectView. The user clicks the button, the VisualEffectView blurs everything else, and the PickerView displays all the names in their contact list (I'm using SwiftAddressBook to do this.)
This works fine except when the user clicks the button, the UI locks up for about 5-10 seconds. I can't find any evidence of heavy CPU or memory usage. If I just print the sorted array to the console, it happens almost immediately. So something about showing the window is causing this bug.
#IBAction func getBffContacts(sender: AnyObject) {
swiftAddressBook?.requestAccessWithCompletion({ (success, error) -> Void in
if success {
if let people = swiftAddressBook?.allPeople {
self.pickerDataSource = [String]()
for person in people {
if (person.firstName != nil && person.lastName != nil) {
//println("\(person.firstName!) \(person.lastName!)")
self.pickerDataSource.append(person.firstName!)
}
}
//println(self.pickerDataSource)
println("done")
self.sortedNames = self.pickerDataSource.sorted { $0.localizedCaseInsensitiveCompare($1) == NSComparisonResult.OrderedAscending }
self.pickerView.reloadAllComponents()
self.blurView.hidden = false
}
}
else {
//no success, access denied. Optionally evaluate error
}
})
}
You have a threading issue. Read. The. Docs!
requestAccessWithCompletion is merely a wrapper for ABAddressBookRequestAccessWithCompletion. And what do we find there?
The completion handler is called on an arbitrary queue
So your code is running in the background. And you must never, never, never attempt to interact with the user interface on a background thread. All of your code is wrong. You need to step out to the main thread immediately at the start of the completion handler. If you don't, disaster awaits.

Resources