I am still very new to programming and sometimes it bites me with very basic concepts.
I have an activity indicator defined in my tableviewcontroller as an Outlet.
#IBOutlet weak var activityIndicator: UIActivityIndicatorView!
The data download to fill the tableview with data is done in a separate file in a class wiht download functions. These functions include the completion handler for the download. Now, if I want to insert the
activityIndicator.stopAnimating()
in the completion part then I get the message "use of unresolved identifier activityIndicator". How can I make the acitivityIndicator a global property, respectively, how can I make the download class/functions recognise the activityIndicator which is defined in the tableViewController? I know this is probably a stupid question for most of you, but I just don't know how to resolve this.
Ideally you don't want the download code to "know" about the activityIndicator. When your viewController calls the download, you could pass another completion handler. Then when the download completion handler runs, call this new completion handler. The viewController knows about the activityIndicator, so it can then stop it. Something (very roughly) along the lines of:
// In ViewController
myThing.doTheDownload(completion: {
dispatch_async(dispatch_get_main_queue(), {
self.activityIndicator.stopAnimating()
})
})
// In download code
func doTheDownload(completion completionHandler: (() -> Void)) {
download(completion: {
completionhandler()
})
}
Note that activityIndicator is a UI element, and therefore its code must run on the main thread.
Related
I am wondering if there is any way that can solve the ViewModel executing before the AppDelegate in my macOS app.
Suppose I have these files:
AppDelegate.swift
final class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ notification: Notification) {
UserDefaults.shared.register(defaults: [ // <--- breakpoint 1
"userDefaultItem1" : false,
"userDefaultItem2" : 0
])
}
}
AppName.swift
#main
struct AppName: App {
#NSApplicationDelegateAdaptor(AppDelegate.self)
private var appDelegate // <--- breakpoint 2
#StateObject
var vm = ViewModel() // <--- breakpoint 3
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(vm)
}
}
}
When I run the application, it hits breakpoint 2, then breakpoint 3, then breakpoint 1.
As a result, none of the UserDefaults are registered prior to access in the ViewModel and it crashes.
Is this something that is supposed to happen, and though the use of AppDelegates are not required, there is use in legacy code that is now moved over to SwiftUI.
if you want to make sure that something is processed before even AppDelegate gets its delegate methods called then you could also place UserDefaults code in init of your AppDelegate. Or also possible, simply move the code over to applicationWillFinishLaunching which is called earlier.
init() { .. } will work because NSObjects always follow the alloc-init pattern. So init gets called first (second after alloc) before even any delegate method is called.
Also when your App crashes when UserDefaults are missing, then it is worth testing what happens when there are no UserDefaults used at all. This will force you to code a fallback and then you apply UserDefaults onto those prepared fallback properties "refreshing" them and go on from there. That makes sure when a User opens your app the first time, where most likely there are no UserDefaults set, it will not crash.
In short: using UserDefaults without fallback is a design pattern issue. You should always provide evaluation of UserDefaults before applying and sometime even before changing them, because Users can even erase them or otherwise manipulate them on a mac.
PS: your ViewModel is called very likely via an entry in Storyboard or main nib and is accordingly marked in Info.plist to do so.
another point to think about is, when this particular UserDefault is needed for the ViewModel then it is also worth to recall and evaluate them in init() of the ViewModel instead of AppDelegate
I'm developing a custom class in Swift based on NSObject. It's a statusMenu icon/menu helper. When I receive an event for the icon being clicked in my custom class, I want to pass this on in the same way an NSButton allows to create an IBAction to respond to the user clicking the button.
How do I do this?
Code:
I'm registering a selector in my class to listen to clicks:
statusItem.action = #selector(statusBarIconClicked)
The selector receiving this:
#objc func statusBarIconClicked(sender: AnyObject) {
print("clicked clicked!!")
// pass sent action on through a new sent action... how?
}
I want this to be linkable to the user in the same way a button can lead to this:
#IBAction func myClassSaysMenuWasClicked(_ sender: Any) {
// Reacting to that
}
Googled for a good while and found: nothing.
I take it that you're asking about this sort of thing, displayed in the Connections inspector (this is iOS, not macOS, but it's the same idea):
The question would then be: when the user selects an instance of my class in the nib editor in Xcode, I'd like those Sent Events to appear in the Connections inspector so that the user can hook up one of them and use the target-action architecture.
You can do this only if your class is a Control subclass. Thus, for example, in iOS, a custom UIControl subclass displays those sent events in Interface Builder.
If you can't do that, then the programmer won't be able to configure your target-action in Interface Builder. You can certainly implement a target–action architecture, but the programmer will have to set the target and action in code. (You could do half a job of it by making the target an outlet, of course.)
I worked around the comment above and googled further. I found the solution being to change from NSObject to NSController in this line:
class StatusMenuController: NSControl, NSMenuDelegate {
And run this command when I want to trigger the sent action:
if let theAction = self.action { NSApp.sendAction(theAction, to: self.target, from: self) }
The if-command of course checking so that an action is actually set before trying to use it.
I found no ways during my research to add any further sent actions. The way to go here seems to be delegates.
In a MacOS app, I have a callback function called by another module (which is a network module that gets some response from the Internet to fire the callback):
func callbackHandler() {
someViewController.updateSomeView()
}
In this callback handler, a view controller is called to update some view, and inside the view controller, reload data for a table:
func updateSomeView() {
someTable.reloadData()
}
However, by doing so, I will get an error like this:
Main Thread Checker: UI API called on a background thread: ...
Therefore, I have to add DispatchQueue.main.async() either in the caller :
func callbackHandler() {
DispatchQueue.main.async() {
someViewController.updateSomeView()
}
}
or the callee:
func updateSomeView() {
DispatchQueue.main.async() {
someTable.reloadData()
}
}
Either way will solve the problem. But I think it's quite weird for the caller or the callee to be aware of that UI API called is going to be called on a background thread by adding DispatchQueue.main.async() as a fix.
How to do it in a proper way in terms of design architecture?
What you're doing, and the API, is perfectly reasonable. It is quite normal for asynchronous callbacks to arrive on a background thread (esp. in connection with networking), and for the callee to have to step out to the main thread in order to touch the user interface. Don't worry, be happy.
I thought that Today View every time when i open it it calls "viewWillAppear" but its not. When i change something in my app, and then I slide down for Today View it sometimes refresh the view and sometimes not.
I do all logic in viewWillAppear (fetch data from coreData and put that data to labels), but its not called everytime.
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
fetchContent()
setLabels()
setContentHeight()
tableView.reloadData()
print("view will appear")
}
how to call fetchContent and setLabels every time when user opens Today Extensions?
For this matter you should be using NSWidgetProvinding's widgetPerformUpdateWithCompletionHandler.
Steps:
1.- Make sure that your UIViewController implements NCWidgetProviding
class MainViewController: UIViewController, NCWidgetProviding
2.- Add the following function:
func widgetPerformUpdateWithCompletionHandler(completionHandler: ((NCUpdateResult) -> Void)) {
// Perform any setup necessary in order to update the view.
// If an error is encountered, use NCUpdateResult.Failed
// If there's no update required, use NCUpdateResult.NoData
// If there's an update, use NCUpdateResult.NewData
completionHandler(NCUpdateResult.NewData)
}
3.- In your case you will be using .NewData.
Just make sure that you retrieve needed data and refresh your views (putting every data in place, filling labels, graphs, etc).
Nevermind the fact that your view is not visible during the call to this function, iOS will fill the view and take a snapshot of it.
Then that's what it shows while you are opening notification center and until you gain control again of your app.
So, in your case would be something like this:
func widgetPerformUpdateWithCompletionHandler(completionHandler: ((NCUpdateResult) -> Void)) {
fetchContent()
setLabels()
setContentHeight()
tableView.reloadData()
completionHandler(NCUpdateResult.NewData)
}
Swift 2.1 && Xcode 7.2
It looks like some bug appears when you many time recompile this today extension. solution is to remove from notification center and add it again. then it refresh fine everytime opened
I've only been working with Swift for the last month please forgive me if I missed something everyone should know. I have been tasked with creating an app that will receive an APN (Apple Push Notification) containing a the document number of a sales order that requires approval. The app will display details about the sales order and present the user with an accept or reject option. I built a proof of concept that works perfectly with the APN. When I add the APN functionality I create an instance of the ViewController and use it to get the order details. I stored the order details in a global variable that is available to both classes (delegate and ViewController). Everything works great until I try to write order details to the screen. It fails with:
"fatal error: unexpectedly found nil while unwrapping an Optional
value"
In AppDelegate.swift I set
var vc = ViewController.self
In func
application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject], fetchCompletionHandler completionHandler:(UIBackgroundFetchResult) -> Void)
I process the APN and call
vc.init().LogonServiceLayer()
Everything works great until it tries to write to the screen. I try to write to CardNameLabel.text. I have CardNameLabel defined as
#IBOutlet weak var CardNameLabel: UILabel!
I'm certain that I must create some sort of the an initialization function/constructor for my class. I've tried something like:
required init(coder aDecoder: NSCoder!) {
super.init(coder: aDecoder)!
// Your intializations
CardNameLabel.text = String ()
}
required init() {
super.init(nibName: nil, bundle: nil)
//Do whatever you want here
CardNameLabel.text = String ()
}
This will compile but now it fails in the constructor instead of when I am trying to update the label on the screen. Please forgive me if I'm missing something really obvious but so far I haven't found a way to update the labels on the screen using the instance of my class created in the delegate module.
What am I missing?
Thank you,
Duncan