How to detect switch between macOS default & dark mode using Swift 3 - swift

I want to change my status bar app icon when the user switches from default to dark mode and vice versa (using Swift 3). Here’s what i have so far:
func applicationDidFinishLaunching(_ aNotification: Notification) {
DistributedNotificationCenter.default().addObserver(self, selector: #selector(darkModeChanged(sender:)), name: "AppleInterfaceThemeChangedNotification", object: nil)
}
...
func darkModeChanged(sender: NSNotification) {
print("mode changed")
}
Unfortunately, it’s not working. What am I doing wrong?

I'm using this Swift 3 syntax successfully:
DistributedNotificationCenter.default.addObserver(self, selector: #selector(interfaceModeChanged(sender:)), name: NSNotification.Name(rawValue: "AppleInterfaceThemeChangedNotification"), object: nil)
func interfaceModeChanged(sender: NSNotification) {
...
}

Swift 5, Xcode 10.2.1, macOS 10.14.4
Great stuff. My two cents around #Jeffrey's answer:
extension Notification.Name {
static let AppleInterfaceThemeChangedNotification = Notification.Name("AppleInterfaceThemeChangedNotification")
}
So one could (instead of rawValue):
func listenToInterfaceChangesNotification() {
DistributedNotificationCenter.default.addObserver(
self,
selector: #selector(interfaceModeChanged),
name: .AppleInterfaceThemeChangedNotification,
object: nil
)
}
Remember the #objc attribute:
#objc func interfaceModeChanged() {
// Do stuff.
}

If you simply need to update icon images for dark mode, you can do this without notifications by creating a dynamic image that updates automatically.
From Apple's documentation:
To create an image that draws its content dynamically, use the init(size:flipped:drawingHandler:) method to initialize your image with a custom drawing handler block. AppKit calls your handler block whenever the system appearance changes, giving you a chance to redraw the image using the new appearance.

So, my little additions as well:
enum InterfaceStyle: String {
case Light
case Dark
case Unspecified
}
extension Notification.Name {
static let AppleInterfaceThemeChangedNotification = Notification.Name("AppleInterfaceThemeChangedNotification")
}
extension NSViewController {
var interfaceStyle: InterfaceStyle {
let type = UserDefaults.standard.string(forKey: "AppleInterfaceStyle") ?? "Unspecified"
return InterfaceStyle(rawValue: type) ?? InterfaceStyle.Unspecified
}
}
and somewhere in a NSViewController:
DistributedNotificationCenter.default.addObserver(forName: .AppleInterfaceThemeChangedNotification,
object: nil, queue: OperationQueue.main) {
[weak weakSelf = self] (notification) in // add an observer for a change in interface style
weakSelf?.setAppearance(toStyle: weakSelf!.interfaceStyle)
}
where setAppearance reacts on the change of style.

Related

Swift Macos GameController not showing. Controller.count return zero

I'm developing an app which includes Gamecontroller. I'm trying to show the connected controller count, but it´s not working.
Here is my simple code:
import Cocoa
import GameController
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ aNotification: Notification) {
// Insert code here to initialize your application
// - MARK: Controllers
let controllers = GCController.controllers()
print(controllers.count)
}
func applicationWillTerminate(_ aNotification: Notification) {
// Insert code here to tear down your application
}
}
it always prints 0
What am I doing wrong?
EDIT:
The problem was i mised to add:
let ctr = NotificationCenter.default
ctr.removeObserver(self, name: .GCControllerDidConnect, object: nil)
ctr.removeObserver(self, name: .GCControllerDidDisconnect, object: nil)
To handle when a controller is connected
Fixed using this tutorial: tvOS Games, Part 1: Using the Game Controller Framework
The next problem is to find a unique identifier for the device...
EDIT:
The problem was i mised to add:
let ctr = NotificationCenter.default
ctr.removeObserver(self, name: .GCControllerDidConnect, object: nil)
ctr.removeObserver(self, name: .GCControllerDidDisconnect, object: nil)
To handle when a controller is connected

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
}

KVO on SKView is not being called

I am trying to do KVO on an property of SKView but its not working.
I have tried it with .frame and it works like a charm, so why not also on .window ?
To clarify, I am using SpriteView in a SwiftUI app and I am trying to get the bounds of the View.
What I am after is to get the following before the App starts;
print (self.convertPoint(fromView: .zero) ). When I use this in
override func didMove
I'll get Nan/NaN. However Apple Code Level Support said this about using SpriteView and getting the bounds of a view.
The reason you are receiving NaN is that you are calling these methods
before the underlying SKView has been actually presented by SwiftUI,
which is an event that you have no visibility into, and no way to call
code when it happens.
However, when this event does occur, the SKScene’s view property will
have it’s window set from nil to a UIWindow. Therefore, you could use
KVO to observe when the window property is changed, and then make your
calls to convertPoint once there is a non-nil window.
I have so far this:
override func didMove(to view: SKView) {
observe.observe(object: view )
}
class Observer:SKScene {
var kvoToken: NSKeyValueObservation?
func observe(object: SKView) {
kvoToken = object.observe(\.window , options: [ .new] ) { (object, change) in
guard let value = change.newValue else { return }
print("New value is: \(value)")
print ("NEW", self.convertPoint(fromView: .zero) )
}
}
deinit {
kvoToken?.invalidate()
}
}
I have also tried to add an observer like so :
NotificationCenter.default.addObserver(view.window, selector: #selector(test(_:)), name: NSNotification.Name(rawValue: "TestNotification"), object: nil)
The above doesn't seem to do anything. So I am kinda stuck, any help would be appreciated.
The answer (probably) ..
I couldn't read the '.window' property of UIView, so I started to look for another observable property that would change as soon as UIWindow is != nil. I think I have found it in SKScene.view.frame . I am not entirely sure that this is a 100% good answer but it works.
class Observer: NSObject {
dynamic var kvoToken: NSKeyValueObservation?
func observe(object: SKScene ) {
kvoToken = object.observe(\.view?.frame , options: [ .new] ) { (object, change) in
guard let value = change.newValue else { return }
print("New value is: \(value)")
print ("CONVERTING", object.convertPoint(fromView: .zero) )
}
}
deinit {
kvoToken?.invalidate()
}
}
override func didMove(to view: SKView) {
viewer = view.scene
observe.observe(object: viewer )
}

KVO not working (swift 2.2)?

In my app, OneVC is one of child ViewControllers of PageViewController, TwoVC is the embed view controller of OneVC's Container View.
When the user drag the scroll view in OneVC, I want the drag action can be not just update the content in OneVC from web API, but notify TwoVC to update too.
Both OneVC and TwoVC will appear at interface at the same time when launch.
I'm following Apple's "Using Swift with Cocoa and Objective-C" "Key-Value Observing" instruction to imply KVO, but no notification is sent when the observed property changes. Please see below my code:
OneVC is the object to be observed.
class OneVC: UIViewController, UIScrollViewDelegate {
dynamic var isDragToUpdate = false
func scrollViewDidEndDragging(scrollView: UIScrollView, willDecelerate decelerate: Bool) {
if scrollView.contentOffset.y < -150 {
if isDragToUpdate {
isDragToUpdate = false
} else {
isDragToUpdate = true
}
print(isDragToUpdate)
}
}
}
TwoVC is the observer
class TwoVC: UIViewController {
let oneVC = OneVC()
override viewDidLoad() {
oneVC.addObserver(self, forKeyPath: "isDragToUpdate", options: [], context: nil)
}
override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
print("hoh")
guard keyPath == "isDragToUpdate" else {return}
print("hah")
}
deinit {
oneVC.removeObserver(self, forKeyPath: "isDragToUpdate")
}
}
I checked row by row, and find many other stackoverflow answers, but still no idea what's going wrong on my code, when drag and release the scrollview, none of "hoh" and "hah" are print in console, except print(isDragToUpdate) is printed properly.
Thank you in advance!

NSComboBox getGet value on change

I am new to OS X app development. I manage to built the NSComboBox (Selectable, not editable), I can get it indexOfSelectedItem on action button click, working fine.
How to detect the the value on change? When user change their selection, what kind of function I shall use to detect the new selected index?
I tried to use the NSNotification but it didn't pass the new change value, always is the default value when load. It is because I place the postNotificationName in wrong place or there are other method should use to get the value on change?
I tried searching the net, video, tutorial but mostly written for Objective-C. I can't find any answer for this in SWIFT.
import Cocoa
class NewProjectSetup: NSViewController {
let comboxRouterValue: [String] = ["No","Yes"]
#IBOutlet weak var projNewRouter: NSComboBox!
#IBAction func btnAddNewProject(sender: AnyObject) {
let comBoxID = projNewRouter.indexOfSelectedItem
print(“Combo Box ID is: \(comBoxID)”)
}
#IBAction func btnCancel(sender: AnyObject) {
self.dismissViewController(self)
}
override func viewDidLoad() {
super.viewDidLoad()
addComboxValue(comboxRouterValue,myObj:projNewRouter)
self.projNewRouter.selectItemAtIndex(0)
let notificationCenter = NSNotificationCenter.defaultCenter()
notificationCenter.addObserver(
self,
selector: “testNotication:”,
name:"NotificationIdentifier",
object: nil)
NSNotificationCenter.defaultCenter().postNotificationName("NotificationIdentifier", object: projNewRouter.indexOfSelectedItem)
}
func testNotication(notification: NSNotification){
print("Found Combo ID \(notification.object)")
}
func addComboxValue(myVal:[String],myObj:AnyObject){
let myValno: Int = myVal.count
for var i = 0; i < myValno; ++i{
myObj.addItemWithObjectValue(myVal[i])
}
}
}
You need to define a delegate for the combobox that implements the NSComboBoxDelegate protocol, and then use the comboBoxSelectionDidChange(_:) method.
The easiest method is for your NewProjectSetup class to implement the delegate, as in:
class NewProjectSetup: NSViewController, NSComboBoxDelegate { ... etc
Then in viewDidLoad, also include:
self.projNewRouter.delegate = self
// self (ie. NewProjectSetup) implements NSComboBoxDelegate
And then you can pick up the change in:
func comboBoxSelectionDidChange(notification: NSNotification) {
print("Woohoo, it changed")
}