UI is not updating after Protocol call Xcode - ios

I was trying to implement viper architecture on my Xcode. I am following an article https://medium.com/cr8resume/viper-architecture-for-ios-project-with-simple-demo-example-7a07321dbd29.
also, I downloaded the article source code, and run well also UI is changing. but when I created a new project with swift 5 and copy all methods and classes. after running UI is not updating but both codes are the same. please check below
This is a single-page sample code
please check this Github for my project https://github.com/Faizulkarim/movieHut/tree/main/MovieHutWithViper
This is my protocol
protocol PresenterToViewProtocol: AnyObject{
func showMovieList(MovieList: Array<movieModel>)
}
Here o call showMovieListMethod
extension MoviePresenter : InteractorToPresenterProtocol {
func movieFetchSuccess(movieModelArray: Array<movieModel>) {
view?.showMovieList(MovieList: movieModelArray)
}
}
// on MovieListViewController confirmed delegate
extension MovieListViewController:PresenterToViewProtocol{
func showMovieList(MovieList: Array<movieModel>) {
self.Movies = MovieList
self.cover.backgroundColor = UIColor.red
print(self.movies.count)
self.trandingTableView.reloadData()
}
}
on showMovieList function call, it's printing the total count of movies. But when I reloadtableview() table view is not interacting is showing any data. Even when I set to change the background of a view it's not changing. when I set breakpoint is' stop. So protocol is called properly but UI is not updating. i search in google didn't find relevant answer.

You might be in a background thread. You can only update UI from main thread.
Try to update like this:
DispatchQueue.main.async {
self.trandingTableView.reloadData()
}

Related

Swift: Update UI - Entire function on main thread or just the UI update?

I've read that the UI should always be updated on the main thread. However, I'm a little confused when it comes to the preferred method to implement these updates.
I have various functions that perform some conditional checks then the result is used to determine how to update the UI. My question is should the entire function run on the main thread? Should just the UI update? Can / should I run the conditional checks on another thread? Does it depend on what the function does or how fast you want it done?
Example a function that changes the image inside an ImageView without threading:
#IBAction func undoPressed(_ sender: Any) {
if !previousDrawings.isEmpty {
previousDrawings.remove(at: previousDrawings.count - 1)
if let lastDrawing = previousDrawings.last {
topImageView.image = lastDrawing
}
else {
// empty
topImageView.image = nil
}
}
}
Should I be setting topImageView.image on the main thread? Like this:
#IBAction func undoPressed(_ sender: Any) {
if !previousDrawings.isEmpty {
previousDrawings.remove(at: previousDrawings.count - 1)
if let lastDrawing = previousDrawings.last {
DispatchQueue.main.async {
self.topImageView.image = lastDrawing
}
}
else {
DispatchQueue.main.async {
self.topImageView.image = nil
}
}
}
}
Should I be using a background thread for the conditional checks? Like this:
#IBAction func undoPressed(_ sender: Any) {
DispatchQueue.global(qos: .utility).async {
if !previousDrawings.isEmpty {
previousDrawings.remove(at: previousDrawings.count - 1)
if let lastDrawing = previousDrawings.last {
DispatchQueue.main.async {
self.topImageView.image = lastDrawing
}
}
else {
DispatchQueue.main.async {
self.topImageView.image = nil
}
}
}
}
}
If someone could explain what method is preferred and why that would be really helpful.
Back up. Except in special circumstances, all your code is run on the main thread. UIAction methods, for example, are ALWAYS executed on the main thread, as are all the methods defined by UIViewController and it's various subclasses. In fact, you can safely say that UIKit methods are performed on the main thread. Again, your methods will only be called on a background thread in very special circumstances, which are well documented.
You can use GCD to run blocks of code on background threads. In that case, the code is being run on a background thread because you explicitly asked for that to happen.
Some system functions (like URLSession) call their delegate methods/run their completion handlers on background threads by default. Those are well documented. For third party libraries like AlamoFire or FireBase, you'll have to read the documentation, but any code that's called on a background thread should be very well documented because you have to take special precautions for code that runs on a background thread.
The usual reason to use a background thread is so that a long-running task can run to completion without freezing the user interface until it's done.
A common pattern for, example, is using URLSession to read some JSON data from a remote server. The completion handler is called on a background thread since it might take time to parse the data you get back. Once you are done parsing it, though, you'd wrap a call to update the UI in a GCD call to the main thread, since UI changes must be performed on the main thread.
First off, your undoPressed method will be called on the main queue.
In the first set of code, everything will be on the main queue.
In the second set of code, using DispatchQueue.main.async is pointless since the rest of the code is already on the main queue.
So really your only two sensible options are 1 and 3.
Given your code, option 1 is fine. You would only want to use option 3 if the code being run in the background took more than a trivial amount of time to execute. Since the code you have here is trivial and will take virtually no time to execute, there is no point in option 3 here.
So simply use your first set of code and you'll be fine.
Worry about moving code to the background when it need to perform a big loop or calculate a complicated algorithm or perform any sort of network access.
To make it simple, make the calculation and then everything related to that updated calculation that needs to be reflected in the UI should be done from:
DispatchQueue.main.async{ //code }
that is using main thread.

Ensure that property observer didSet manipulates User Interface after viewDidLoad

I am working on an open source tutorial using MVVM, Coordinators and RxSwift. I am constructing all the viewcontrollers and models in the coordinator. Controller has a strong reference to viewmodel and when a viewmodel is set, I would like to perform some UI related actions(using property observer didSet). The problem I am facing is that didSet is called before viewDidLoad causing a crash.
Stripped down version of ViewController:
class MessageVC: UIViewController {
var viewModel: MessageViewModel! {
didSet {
manipulateUI() // crashes
}
}
override func viewDidLoad() {
super.viewDidLoad()
manipulateUI() // works fine if setup is correct in coordinator
}
Coordinator stripped down version:
extension AppCoordinator {
convenience init() {
let rootVC = MessageVC() // actual construction from storyboard
let messages = Message.getMessages()
rootVC.viewModel = MessageViewModel(withMessage: messages)
}
My concern is that even though calling manipulateUI in viewDidLoad is working for me currently, the app will crash if I forget to set the viewModel from my co-ordinator making me think that I am using a fragile architecture. I really like updating userinterface from didSet but it is called before viewDidLoad.
I know it is a simple problem but from architecture standpoint it seems fragile. Any suggestions, improvements and comments are appreciated a lot.
I wont say that cases like this can define wether you are dealing with fragile architecture or not because view controllers has their own life cycle which differs a lot from other objects life cycle. Anyway you can easily avoid crashes here using different approaches. For example :
Approach 1:
Put a guard statement at the very beginning of your manipulateUI function so this function wont manipulate UI until both view is loaded and model is set. Then call this function on viewDidLoad method and when viewModel is set:
func manipulateUI(){
guard let viewModel = self.viewModel , isViewLoaded else {
return
}
//continue manipulation here
}
Approach 2:
Since you are not sure wether view is loaded when you set the model and don't know if views are initialized yet, you can access the views as optional properties in manipulateUI function:
func manipulateUI(){
self.someLabel?.text = self.viewModel.someText
//continue manipulation here
}
Approach 3:
Since you are using RxSwift you can always register an observer for view controller's isViewLoaded property and set the data source after you are sure that view is loaded
Crash happens because at this point
rootVC.viewModel = MessageViewModel(withMessage: messages)
view controller is not initialized.
It won't work the way you're trying to accomplish, you have to call manipulateUI() inside viewDidLoad.

Updating a UILabel Through A Function....Is There Something I'm Missing?

I asked a question similar to this one earlier but this question is more about the general language and fundamentals of Swift. In the code below, shouldn't this function technically work and change the label text? I've been running it for a while now and every time it fails. I've made sure that all of my outlets are linked properly as well. Sorry if the answer is obvious, I'm new to Swift.
func changeLabel() {
DispatchQueue.main.sync(execute: {
self.testText.text = "YES"
})
}
override func viewDidLoad() {
super.viewDidLoad()
let city:String? = nil
if city == nil {
changeLabel()
}
}
viewDidLoad is always called from the main thread (unless a programmer mistakenly does otherwise - and that's a whole other problem).
So there is no point to using DispatchQueue.main.sync to update the label. In fact, it's bad in this case. Calling DispatchQueue.main.sync when already on the main queue will cause the app's user interface to hang until the app is killed.
You have two choices:
Remove the use of DispatchQueue.main.sync since it's not needed in the code you posted.
Change sync to async. This fixes the problem with the app user interface hanging and it also allows you call the changeLabel method from any queue and work properly.
Use this instead:
func changeLabel() {
DispatchQueue.global().async(execute: {
print("teste")
DispatchQueue.main.sync{
self.testText.text = "YES"
}})
Thanks

Controlling app flow in swift

I am trying to build an app which is dependent on having some items in CoreData. I have it syncing with an external data source, which all works fine.
my app uses three methods, and is a single view app:
syncData()
createSpinner()
showResult()
now createSpinner is dependent on having some data in CoreData - and only needs to run once
showResult is dependent on the 'Spinner' having been created, and is called once on creation to initialize itself, as well as each time my spinner has been spun
I currently have SyncData in the viewDidLoad(), and the createSpinner() in viewDidAppear() (as it changes size depending on screen size)
The problem is on first launch the data does not load in time for the createSpinner(), and thus the app looks useless. How can I 'wait' for that first sync, or set up something to check there is some data?
The solution is to force syncData() & createSpinner() to run in same thread
you can do this by creating a serial queue and dispatch both methods asynchronously into it
let serialQueue = dispatch_queue_create("com.mycompany.myview", DISPATCH_QUEUE_SERIAL);
override func viewDidLoad() {
super.viewDidLoad()
dispatch_async(serialQueue) {
syncData()
}
}
override func viewDidAppear() {
super.viewDidAppear()
dispatch_async(serialQueue) {
createSpinner()
}
}

update text field ui in swift ios

i meet a question when i'm using swift of ios SDK 8
I used a callback to fetch data background and when data fetched, i want to set a text field value and update screen.
here is my code:
#IBOutlet var txtTest: UITextField!
#IBAction func login() {
let username = "aaa";
let password = "bbb";
var res = ServiceClient.login(username, password: password){
(res:String, error:String?) -> Void in if(error == nil){
self.txtTest.text = res;
}
}
}
Run this code and the data is correctly fetched and the txtTest did not updated, but when i tap txtTest, the value will be shown. So is there any way to force update UI? or send a message to UI thread?
Same issue that everyone is having when wanting to do UI updates in Swift: do not ever update the UI in any secondary thread. And that means anything with closures. Since Swift is using closures so much, that issue is being seen a lot more but it isn't new.
See Swift Update Label (with HTML content) takes 1min for the correct way of doing things.
UIKit is not thread-safe and should only be updated from the main thread. Downloads are done on the background thread, and you cannot update UI from there. Try:
For swift 3 and swift 4
DispatchQueue.main.async {
self.txtTest.text = "Your text"
}
Related duplicate question but with specific answer
iOS label does not update text with function in Swift

Resources