Bug in swift5. UITableView.reloadData() must be used from main thread only, UITableViewController.tableView must be used from main thread only - swift5

import UIKit
class CoursesController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(forName: NSNotification.Name(rawValue: "dataRefreshed") , object: nil, queue: nil) {
(notification) in
self.tableView.reloadData()
self.navigationItem.title = Model.shared.currentDate
}
navigationItem.title = Model.shared.currentDate
}

That was always a requirement. Calling reloadData() from a background thread was always a bug in your code - I assume iOS only tells you now.
Make sure not to call reloadData() on a background thread. I have a four line method that you can call as
run_on_main_thread {
// Whatever you want to do on the main thread
}

Related

difference between function with #objc in front and function doesn't have #objc

In one of the view controller files in my project, there are two functions, one is called in viewdidload and another is called by Notification and observers. Those functions do exactly the same thing, and I was wondering if I get rid of one of the functions, especially the one without using #objc in front. (otherwise I get an error)
override func viewDidLoad() {
super.viewDidLoad()
configureNotifications()
displayItems()
}
func displayItems() {
fetchLiveEvents { [weak self] in
self?.applySnapshot(animatingDifferences: true)
}
}
func configureNotifications() {
NotificationCenter.default.addObserver(self, selector: #selector(updateExistingItem), name: .updateExistingItem, object: nil)
}
#objc func updateExistingItem() {
fetchLiveEvents { [weak self] in
self.applySnapshot(animatingDifferences: true)
}
}
Since I'm using the notification canter, I cannot get rid of #objc in front of updateExistingItem function. However, the updateExistingItem and displayItems are doing exactly something, so I feel it's kinda redundant and I was thinking to get rid of displayItems function from the viewDidLoad and call updateExistingItem (probably change the name) in viewdidLoad instead.
Is there any convention in Swift programming that keeps both #objc and normal function when they are doing the same thing? or is it just a personal preference and doesn't matter to leave both of them?
viewDidLoad just call once when the screen is present if you go to another screen by pushing a viewcontroller or presenting a controller and comeback to this controller the viewDidLoad didn't triggered it will never called again until the next run / terminate the app and open again.
so your function is called by the notification to run again when this screen appear.
// just called once
override func viewDidLoad() {
super.viewDidLoad()
configureNotifications()
displayItems()
}
// just called every time when you popped a viewController or tap on tab bar items using tabbar controller
override func viewWillAppear() {
super.viewDidLoad()
displayItems()
}
in your scenario may be you came back to this screen by present some other screen and do some functionality there and call the notification to be trigger on this screen so nothing will trigger if your present a screen by modal presentation style over full screen
That's why called the notification to start displaying item again
override func viewDidLoad() {
super.viewDidLoad()
configureNotifications()
displayItems()
}
// called once
func displayItems() {
fetchLiveEvents { [weak self] in
self?.applySnapshot(animatingDifferences: true)
}
}
func configureNotifications() {
NotificationCenter.default.addObserver(self, selector: #selector(updateExistingItem), name: .updateExistingItem, object: nil)
}
// called every time when you trigger notifcation
#objc func updateExistingItem() {
fetchLiveEvents { [weak self] in
self.applySnapshot(animatingDifferences: true)
}
}

Swift calling a ViewController function from the AppDelegate [duplicate]

I am building an iOS app using the new language Swift. Now it is an HTML5 app, that displays HTML content using the UIWebView. The app has local notifications, and what i want to do is trigger a specific javascript method in the UIWebView when the app enters foreground by clicking (touching) the local notification.
I have had a look at this question, but it does not seem to solve my problem. I have also come across this question which tells me about using UIApplicationState, which is good as that would help me know the the app enters foreground from a notification. But when the app resumes and how do i invoke a method in the viewController of the view that gets displayed when the app resumes?
What i would like to do is get an instance of my ViewController and set a property in it to true. Something as follows
class FirstViewController: UIViewController,UIWebViewDelegate {
var execute:Bool = false;
#IBOutlet var tasksView: UIWebView!
}
And in my AppDelegate i have the method
func applicationWillEnterForeground(application: UIApplication!) {
let viewController = self.window!.rootViewController;
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
var setViewController = mainStoryboard.instantiateViewControllerWithIdentifier("FirstView") as FirstViewController
setViewController.execute = true;
}
so what i would like to do is when the app enters foreground again, i want to look at the execute variable and run the method as follows,
if execute{
tasksView.stringByEvaluatingJavaScriptFromString("document.getElementById('sample').click()");
}
Where should i put the code for the logic to trigger the javascript from the webview? would it be on viewDidLoad method, or one of the webView delegate methods? i have tried to put that code in the viewDidLoad method but the value of the boolean execute is set to its initial value and not the value set in the delegate when the app enters foreground.
If I want a view controller to be notified when the app is brought back to the foreground, I might just register for the UIApplication.willEnterForegroundNotification notification (bypassing the app delegate method entirely):
class ViewController: UIViewController {
private var observer: NSObjectProtocol?
override func viewDidLoad() {
super.viewDidLoad()
observer = NotificationCenter.default.addObserver(forName: UIApplication.willEnterForegroundNotification, object: nil, queue: .main) { [unowned self] notification in
// do whatever you want when the app is brought back to the foreground
}
}
deinit {
if let observer = observer {
NotificationCenter.default.removeObserver(observer)
}
}
}
Note, in the completion closure, I include [unowned self] to avoid strong reference cycle that prevents the view controller from being deallocated if you happen to reference self inside the block (which you presumably will need to do if you're going to be updating a class variable or do practically anything interesting).
Also note that I remove the observer even though a casual reading of the removeObserver documentation might lead one to conclude is unnecessary:
If your app targets iOS 9.0 and later or macOS 10.11 and later, you don't need to unregister an observer in its dealloc method.
But, when using this block-based rendition, you really do need to remove the notification center observer. As the documentation for addObserver(forName:object:queue:using:) says:
To unregister observations, you pass the object returned by this method to removeObserver(_:). You must invoke removeObserver(_:) or removeObserver(_:name:object:) before any object specified by addObserver(forName:object:queue:using:) is deallocated.
I like to use the Publisher initializer of NotificationCenter. Using that you can subscribe to any NSNotification using Combine.
import UIKit
import Combine
class MyFunkyViewController: UIViewController {
/// The cancel bag containing all the subscriptions.
private var cancelBag: Set<AnyCancellable> = []
override func viewDidLoad() {
super.viewDidLoad()
addSubscribers()
}
/// Adds all the subscribers.
private func addSubscribers() {
NotificationCenter
.Publisher(center: .default,
name: UIApplication.willEnterForegroundNotification)
.sink { [weak self] _ in
self?.doSomething()
}
.store(in: &cancelBag)
}
/// Called when entering foreground.
private func doSomething() {
print("Hello foreground!")
}
}
Add Below Code in ViewController
override func viewDidLoad() {
super.viewDidLoad()
let notificationCenter = NotificationCenter.default
notificationCenter.addObserver(self, selector:#selector(appMovedToForeground), name: UIApplication.willEnterForegroundNotification, object: nil)
}
#objc func appMovedToForeground() {
print("App moved to foreground!")
}
In Swift 3, it replaces and generates the following.
override func viewDidLoad() {
super.viewDidLoad()
foregroundNotification = NotificationCenter.default.addObserver(forName:
NSNotification.Name.UIApplicationWillEnterForeground, object: nil, queue: OperationQueue.main) {
[unowned self] notification in
// do whatever you want when the app is brought back to the foreground
}

Is `completion` block of `UIViewController.present` guaranteed to be running on main thread?

Seems a simple question, but I don't see any definitive answer.
In UIViewController function:
func present(_ viewControllerToPresent: UIViewController,
animated flag: Bool,
completion: (() -> Void)? = nil)
Is completion block guaranteed to be running on main thread?
In other words. Can I do this:
vc.present(anotherVC, animated: animated) { [weak self] in
guard let self = self else { return }
// do some UI operations
self.<...>
}
Or should I do this:
vc.present(anotherVC, animated: animated) {
DispatchQueue.main.async { [weak self] in
guard let self = self else { return }
// do some UI operations
self.<...>
}
}
Note: I tried to test it a bunch of times, and seems it's always running on main thread. But that could be by accident, and Apple's doc doesn't say anything explicit.
The completion callback runs on the main thread. There is no reason otherwise since all UI-related manipulations by cocoa are done on the main thread. However you should be calling the present function from the main thread as well.

Using CGDisplayStream with a Queue

I'm working on implementing screen-capturing of a Mac app suing CGDisplayStream, similar to the question asked here, but in Swift.
Below is the code I have in my app's single ViewController:
override func viewDidAppear() {
super.viewDidAppear()
let backgroundQueue = DispatchQueue(label: "com.app.queue",
qos: .background,
target: nil)
let displayStream = CGDisplayStream(dispatchQueueDisplay: 0, outputWidth: 100, outputHeight: 100,pixelFormat: Int32(k32BGRAPixelFormat), properties: nil, queue: backgroundQueue) { (status, code, iosurface, update) in
switch(status){
case .frameBlank:
print("FrameBlank")
break;
case .frameIdle:
print("FrameIdle")
break;
case .frameComplete:
print("FrameComplete")
break;
case .stopped:
print("Stopped")
break;
}
self.update()
}
displayStream?.start()
}
func update(){
print("WORKING")
}
What seems to be happening is that the queue process isn't being properly initialized, but I'm not sure...when the app starts, the self.update() is called once, but only once. Given that the display stream has started properly, I would expect this function to be called repeatedly, but it's only called once.
Anyone have any ideas? Am I not setting up a queue properly?
Thank you!
The problem is that no reference to displayStream is kept outside
of viewDidAppear, so the stream will be deallocated on return
of that method.
Making it a property of the view controller should solve the problem:
class ViewController: NSViewController {
var displayStream: CGDisplayStream?
override func viewDidAppear() {
super.viewDidAppear()
// ...
displayStream = CGDisplayStream(...)
displayStream?.start()
}
override func viewWillDisappear() {
super.viewWillDisappear()
displayStream?.stop()
displayStream = nil
}
}
Releasing the stream in viewWillDisappear breaks the retain cycle
and allows the view controller to be deallocated (if it is part of
a view controller hierarchy).

How do I run an asynchronous thread that only runs as long as the view that uses it is presented?

How do I run an asynchronous thread that only runs as long as the view that uses it is presented?
I want the view to run this asynchronous thread. However, as soon as the view disappears, I want that thread to stop running. What's the best way to do this? I'm not sure where to start and might be thinking about this the wrong way. Nevertheless, what I described is how I want it to behave to the user.
You can use NSOperation to achieve what you want, NSOperation and NSOperationQueue are built on top of GCD. As a very general rule, Apple recommends using the highest-level abstraction, and then dropping down to lower levels when measurements show they are needed.
For example, You want to download images asynchronously when the view is loaded and cancel the task when the view is disappeared. First create a ImageDownloader object subclass to NSOperation. Notice that we check if the operation is cancelled twice, this is because the NSOperation has 3 states: isReady -> isExecuting -> isFinish and when the operation starts executing, it won't be cancelled automatically, we need to do it ourself.
class ImageDownloader: NSOperation {
//1
var photoRecord: NSURL = NSURL(string: "fortest")!
//2
init(photoRecord: NSURL) {
self.photoRecord = photoRecord
}
//3
override func main() {
//4
if self.cancelled {
return
}
//5
let imageData = NSData(contentsOfURL:self.photoRecord)
//6
if self.cancelled {
return
}
}
}
Then you can use it like: downloader.cancel(), downloader.start(). Notice that we need to check if the operation is cancelled in the completion block.
import UIKit
class ViewController: UIViewController {
let downloder = ImageDownloader(photoRecord: NSURL(string: "test")!)
override func viewDidLoad() {
super.viewDidLoad()
downloder.completionBlock = {
if self.downloder.cancelled {
return
}
print("image downloaded")
}
//Start the task when the view is loaded
downloder.start()
}
override func viewWillDisappear(animated: Bool) {
//Cancel the task when the view will disappear
downloder.cancel()
}
}
Once DetailViewController is presented, the asyncOperation method will be executed asynchronously.
Note: currently the asyncOperation method is executed every second so if you want the method to be called only once, you must change the repeats property to false.
class DetailViewController: UIViewController {
// timer that will execute
// asynchronously an operation
var timer: NSTimer!
// counter used in the async operation.
var counter = 0
// when view is about to appear
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
// setting up the timer
timer = NSTimer.scheduledTimerWithTimeInterval(
1.0,
target: self,
selector: #selector(asyncOperation),
userInfo: nil,
repeats: true //set up false if you don't want the operation repeats its execution.
)
}
// when view is about to disappear
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
// stopping the timer
timer.invalidate()
}
// async operation that will
// be executed
func asyncOperation() {
counter += 1
print("counter: \(counter)")
}
}
Source: https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSTimer_Class/
Result: