NSUserNotificationCenter not showing notifications - nsusernotification

I'm not sure if I got something wrong but from the examples I could find. I wrote that little helloworld.
#import <Foundation/Foundation.h>
#import <Foundation/NSUserNotification.h>
int main (int argc, const char * argv[])
{
NSUserNotification *userNotification = [[NSUserNotification alloc] init];
userNotification.title = #"Some title";
userNotification.informativeText = #"Some text";
[[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:userNotification];
return 0;
}
I compile it with:
cc -framework Foundation -o app main.m
It compiles and I can execute it but it won't show anything. For some reasons, nothing gets presented. I read that Notifications may not get displayed if the application is the main actor. How can I make it show Notification?

set a delegate and override
- (BOOL)userNotificationCenter:(NSUserNotificationCenter *)center
shouldPresentNotification:(NSUserNotification *)notification {
return YES;
}

For a complete example in Objective-C and Swift:
We need to provide a NSUserNotificationCenterDelegate, in the simplest case we can use AppDelegate for this
Objective-C
We need to modify AppDelegate provide NSUserNotificationCenterDelegate method. From a clean AppDelegate (which looks like this...)
#interface AppDelegate ()
We'd modify it to be:
#interface AppDelegate () <NSUserNotificationCenterDelegate>
Now we can provide the required delegate method inside #implementation AppDelegate
- (BOOL)userNotificationCenter:(NSUserNotificationCenter *)center
shouldPresentNotification:(NSUserNotification *)notification {
return YES;
}
Swift 4
You can use an extension of AppDelegate to provide NSUserNotificationCenterDelegate methods:
extension AppDelegate: NSUserNotificationCenterDelegate {
func userNotificationCenter(_ center: NSUserNotificationCenter, shouldPresent notification: NSUserNotification) -> Bool {
return true
}
}

Be sure to assign a new identifier for any new notification. A notification will only show once, unless a new identifier is used or the user clears their notification history.

I know it's an old question - but as there may be people looking for an answer how to do this in Python & PyObjC I will post the correct syntax here (as it's one of the top google results). As I can't comment on imp's comment I will post this as another answer.
It's possible to do the same in Python with pyobjc this way:
def userNotificationCenter_shouldPresentNotification_(self, center, notification):
return True
Full class would look like this:
from Cocoa import NSObject
import objc
class MountainLionNotification(NSObject):
""" MacOS Notifications """
# Based on http://stackoverflow.com/questions/12202983/working-with-mountain-lions-notification-center-using-pyobjc
def userNotificationCenter_shouldPresentNotification_(self, center, notification):
return True
def init(self):
self = super(MountainLionNotification, self).init()
if self is None: return None
# Get objc references to the classes we need.
self.NSUserNotification = objc.lookUpClass('NSUserNotification')
self.NSUserNotificationCenter = objc.lookUpClass('NSUserNotificationCenter')
return self
def clearNotifications(self):
"""Clear any displayed alerts we have posted. Requires Mavericks."""
NSUserNotificationCenter = objc.lookUpClass('NSUserNotificationCenter')
NSUserNotificationCenter.defaultUserNotificationCenter().removeAllDeliveredNotifications()
def notify(self, title="", subtitle="", text="", url=""):
"""Create a user notification and display it."""
notification = self.NSUserNotification.alloc().init()
notification.setTitle_(str(title))
notification.setSubtitle_(str(subtitle))
notification.setInformativeText_(str(text))
# notification.setSoundName_("NSUserNotificationDefaultSoundName")
notification.setHasActionButton_(False)
notification.setActionButtonTitle_("View")
# notification.setUserInfo_({"action":"open_url", "value": url})
self.NSUserNotificationCenter.defaultUserNotificationCenter().setDelegate_(self)
self.NSUserNotificationCenter.defaultUserNotificationCenter().scheduleNotification_(notification)
# Note that the notification center saves a *copy* of our object.
return notification

I followed the code on here but was unable to get a notification to work. It turned out that because I was on multiple monitors I needed to check the "When mirroring or sharing the display" option to get the alerts to present themselves. After that the python code worked.

Related

What is the best and quickest way to save a large Class Array in existing project? Realm isn't working

First off, I'm a relative amateur with app design having taught myself high-level swift/xcode last year, so apologies for my code in advance!
I've developed a game which has an array of a 'Player' Class called playerList. I currently convert this playerList array to JSON via Encoder and then save to device...however as my array grows, this exercise is beginning to take a long time, so I'm looking for an alternative. I presume the best solution is to rewrite the app to use CoreDate, SQLite etc, but I'm looking for a quick solution for now.
I could have used userDefaults, however steered away from this as large array and am instead trying to fudge a solution using Realm.
I've attempted the below, but whenever I look at my playerList after loading it is empty. Am I missing something obvious here, or alternatively is there a much better approach than using Realm?
class PlayerArray: Object {
var iden: Int = 0
var allThePlayers: [Player] = playerList
}
func saveViaRealm() {
// Get the default Realm
let realm = try! Realm()
// Define player list
let realmPlayerList = PlayerArray()
realmPlayerList.allThePlayers = playerList
realmPlayerList.iden = 1
// Write to realm
try! realm.write {
realm.add(realmPlayerList)
}
}
func loadViaRealm() {
// Get the default Realm
let realm = try! Realm()
//Retrieve objects from realm
let realmOutputPlayerList = realm.objects(PlayerArray.self)
// Filter to iden required
let realmFiltered = realmOutputPlayerList.filter{$0.iden == 1}[0]
// Assign to playerList
playerList = realmFiltered.allThePlayers
}
I would take a read through the Realm documentation once more around Lists and declaring variables. In your object class are you getting any errors? RealmSwift should be declared with #objc dynamic vars. Also, you shouldn't need but one let = realm. Here is the link to Realm.io documentation.

Pass array from AppDelegate to view controller

I'm loading an array with data from a Firebase database in my AppDelegate as I need the arrays loaded before the views are created and loaded. How can I pass this array to a view controller to populate a TableView?
Edit:
I don't think I worded my question correctly nor provided enough information. I have this Firebase data that I need to load into an array. This array needs to be loaded into the app before it is used by the view controller that uses the array. This is because the view controller uses a cocoa pod that splits the array into a number of categories to be displayed in different tableviews. The repo for the cocoa pod I'm using can be found here.
My question then is where is the best place to load this array? My first thought was the AppDelegate, but the array is empty and as such the table view doesn't load. I'm pretty new to iOS programming so I'm open to any and all suggestions.
In the AppDelegate class:
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]? = nil) -> Bool {
//Fill your array, let's say it's called firebaseArray. I would recommend doing so in the view controller and not in the Appdelegate for better responsibility distribution in your code
let vc = YourViewController()
//And YourViewController must have a array property
vc.array = firebaseArray
window = UIWindow(frame: UIScreen.main.bounds)
//make your vc the root view view controller
window?.rootViewController = vc
window?.makeKeyAndVisible()
return true
}

How can I assert that a value of a string is equal to one of any string values in an array using XCTAssert in Swift?

I'm new to Swift and followed a simple tutorial to make a magic 8 ball Cocoa App that every time I click the ball it shows a different piece of advice. I am now trying to practice my UI automated tests by asserting (XCTAssert) that the "Piece of Advice" label is equal to one of the string values in my array.
My array looks like this and is in my ViewController.swift:
var adviceList = [
"Yes",
"No",
"Tom says 'do it!'",
"Maybe",
"Try again later",
"How can I know?",
"Totally",
"Never",
]
How can I make an assertion in my UITests.swift file that asserts that the string that is shown is equal to one of the string values in the array above?
It's possible that you're asking how to access application state from a UI test, or just in general UI testing.
I think it's a pretty interesting question so I'm going to answer because it's something that I don't know a lot about and hopefully will prompt other people to chime in and correct.
Background: A basic Magic 8 Ball project
I set up a basic project with a view controller that contains two views: a label and a button. Tapping the button updates the label text with a random message:
import UIKit
struct EightBall {
static let messages = ["Yes", "No", "It's not certain"]
var newMessage: String {
let randomIndex = Int(arc4random_uniform(UInt32(EightBall.messages.count)))
return EightBall.messages[randomIndex]
}
}
class ViewController: UIViewController {
let ball = EightBall()
#IBOutlet weak var messageLabel: UILabel!
#IBAction func shakeBall(_ sender: Any) {
messageLabel.text = ball.newMessage
}
}
A basic UI test
Here's a commented UI test showing how to automate tapping on the button, and grabbing the value of the label, and then checking that the value of the label is a valid message.
import XCTest
class MagicUITests: XCTestCase {
// This method is called before the invocation of each test method in the class.
override func setUp() {
super.setUp()
// In UI tests it is usually best to stop immediately when a failure occurs.
continueAfterFailure = true
// UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method.
XCUIApplication().launch()
}
func testValidMessage() {
// Grab reference to the application
let app = XCUIApplication()
// #1
// Grab reference to the label with the accesability identifier 'MessageLabel'
let messagelabelStaticText = app.staticTexts["MessageLabel"]
// Tap the button with the text 'Shake'
app.buttons["Shake"].tap()
// get the text of the label
let messageLabelText = messagelabelStaticText.label
// #2
// check if the text in the label matches one of the allowed messages
let isValidMessage = EightBall.messages.contains(messageLabelText)
// test will fail if the message is not valid
XCTAssert(isValidMessage)
}
}
At #1 The approach that I'm using to get the label is to access the labels accessibilityIdentifier property. For this project I entered this through storyboard, but if you're setting your views up in code you can directly set the accessibilityIdentifier property yourself.
The other thing that's confusing here is that to get access to elements in the view you're not navigating the view hierarchy, but a proxy of the hierarchy, which is why the syntax to get a label is the odd 'staticTexts' (The references at the bottom of the post explain this in more detail).
For #2 I'm inspecting the structure defined in my project. In a unit test you could access this my importing #testable import ProjectName but unfortunately this approach doesn't work for UI Test.
Instead, you'll have to make sure that any source file you want to access from the UI test is included as a target. You can do this in Xcode from this panel by checking the name of your UI test:
More UI testing references:
UI Testing Intro: http://www.mokacoding.com/blog/xcode-7-ui-testing/
UI Testing Cheat Sheet: http://masilotti.com/ui-testing-cheat-sheet/

Reload UICollectionView header or footer?

I have some data that is fetched in another thread that updates a UICollectionView's header. However, I've not found an efficient way of reloading a supplementary view such as a header or footer.
I can call collectionView reloadSections:, but this reloads the entire section which is unnecessary. collectionView reloadItemsAtIndexPaths: only seems to target cells (not supplementary views). And calling setNeedsDisplay on the header itself doesn't appear to work either. Am I missing something?
You can also use (the lazy way)
collectionView.collectionViewLayout.invalidateLayout() // swift
[[_collectionView collectionViewLayout] invalidateLayout] // objc
More complex would be to provide a context
collectionView.collectionViewLayout.invalidateLayout(with: context) // swift
[[_collectionView collectionViewLayout] invalidateLayoutWithContext:context] // objc
You can then make a or configure the context yourself to inform about what should be updated see: UICollectionViewLayoutInvalidationContext
It has a function in there that you can override:
invalidateSupplementaryElements(ofKind:at:) // swift
Another option is (if you have already loaded the correct header/footer/supplementary view) and you only want to update the view with the new data than you can use one of the following functions to retrieve it:
supplementaryView(forElementKind:at:) // get specific one
visibleSupplementaryViews(ofKind:) // all visible ones
Same goes for visible cells with visibleCells. The advantage of just getting the view and not reloading a view entirely is that the cells retains it state. This is espically nice with table view cells when they use swipe to delete/edit/etc since that state is lost after reloading the cell.
If you feel fanatic you can of course also write some extensions to retrieve only cells/supplementary views of a given kind using generics
if let view = supplementaryView(forType: MySupplementaryView.self, at: indexPath) {
configure(view, at indexPath)
}
this assumes that you have a function that registers/dequeues views in example with their class name. I made a post about this here
I just ran into the same problem, and I ended up looking up the view using its tag to edit a label:
UICollectionReusableView *footer = (UICollectionReusableView*)[self.collectionView viewWithTag:999];
UILabel *footerLabel = (UILabel*)[footer viewWithTag:100];
Like you said it is unnecessary to reload an entire section, which cancels out any animation on cells as well. My solution isn't ideal, but it's easy enough.
Swift 3/4/5 version:
collectionView.collectionViewLayout.invalidateLayout()
Caution!
If you change the number of collectionView items at the same time (for example you show the footer only if all cells were loaded), it will crash. You need to reload the data first, to make sure that the number of items is the same before and after invalidateLayout():
collectionView.reloadData()
collectionView.collectionViewLayout.invalidateLayout()
I got the same problem. I tried #BobVorks's answer and it is working fine, if only the cell was reused else it won't. So, I tried finding a more cleaner way to achieve this and I came up reloading the whole UICollectionView after the performBatchUpdate (completion block) and it is working great. It reloads the Collection Without any cancellation of animation in the insertItemsAtIndexPath. Actually I personally up voted recent 2 answers cause i find it working but in my case, this is the cleanest way to do it.
[self.collectionView performBatchUpdates:^{
// perform indexpaths insertion
} completion:^(BOOL finished) {
[self.collectionView reloadData];
}];
[UIView performWithoutAnimation:^{
[self.collectionView reloadSections:[NSIndexSet indexSetWithIndex:4]];
}];
[UIView performWithoutAnimation:^{
[self.collectionView reloadData];
}];
Here are two ways you could do it.
1.
Create a mutable model to back the data that will eventually be available. Use KVO in inherited class of UICollectionReusableView to observe the changes and update the header view with the new data as it comes available.
[model addObserver:headerView
forKeyPath:#"path_To_Header_Data_I_care_about"
options:(NSKeyValueObservingOptionNew |
NSKeyValueObservingOptionOld)
context:NULL];
then implement listener method in header view
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
2.
add notification listener to the view and post a notification when the data has successfully come available. Downside is that this is application wide and not a clean design.
// place in shared header file
#define HEADER_DATA_AVAILABLE #"Header Data Available Notification Name"
// object can contain userData property which could hole data needed.
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(headerDataAvailable:) name:HEADER_DATA_AVAILABLE object:nil];
[[NSNotificationCenter defaultCenter] postNotificationName:HEADER_DATA_AVAILABLE object:nil];
let headerView = collectionView.visibleSupplementaryViews(ofKind: UICollectionView.elementKindSectionHeader)[0] as! UICollectionReusableView
I've used above method to get current header, and successfully updated subviews on it.
Here's what I did to update only the section headers that are currently loaded in memory:
Add a weakToStrong NSMapTable. When you create a header, add the header as the weakly held key, with the indexPath object. If we reuse the header we'll update the indexPath.
When you need to update the headers, you can now enumerate the objects/keys from the NSMapTable as needed.
#interface YourCVController ()
#property (nonatomic, strong) NSMapTable *sectionHeaders;
#end
#implementation YourCVContoller
- (void)viewDidLoad {
[super viewDidLoad];
// This will weakly hold on to the KEYS and strongly hold on to the OBJECTS
// keys == HeaderView, object == indexPath
self.sectionHeaders = [NSMapTable weakToStrongObjectsMapTable];
}
// Creating a Header. Shove it into our map so we can update on the fly
- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath
{
PresentationSectionHeader *header = [collectionView dequeueReusableSupplementaryViewOfKind:kind withReuseIdentifier:#"presentationHeader" forIndexPath:indexPath];
// Shove data into header here
...
// Use header as our weak key. If it goes away we don't care about it
// Set indexPath as object so we can easily find our indexPath if we need it
[self.sectionHeaders setObject:indexPath forKey:header];
return header;
}
// Update Received, need to update our headers
- (void) updateHeaders {
NSEnumerator *enumerator = self.sectionHeaders.keyEnumerator;
PresentationSectionHeader *header = nil;
while ((header = enumerator.nextObject)) {
// Update the header as needed here
NSIndexPath *indexPath = [self.sectionHeaders objectForKey:header];
}
}
#end
This question is very old but a simple way to do it is to just set a delay that covers the time your view is animating and disabling the animation while you update the view...usually a delete or insert takes about .35 seconds so just do:
delay(0.35){
UIView.performWithoutAnimation{
self.collectionView.reloadSections(NSIndexSet(index: 1))
}
My problem arose when frame sizes for the supplementary views changed upon invalidating the layout. It appeared that the supplementary views were not refreshing. It turns out they were, but I was building the UICollectionReusableView objects programmatically, and I was not removing the old UILabel subviews. So when the collection view dequeued each header view, the UILabels would pile up, causing erratic appearance.
The solution was to build each UICollectionReusableView completely inside the viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath method, starting by a) removing all subviews from the dequeued cell, then b) getting the frame size from the item's layout attributes to allow adding the new subviews.
- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath
{
yourClass *header = (yourClass *)[collectionView dequeueReusableSupplementaryViewOfKind:kind withReuseIdentifier:#"identifier" forIndexPath:indexPath];
[[header viewWithTag:1] removeFromSuperview]; // remove additional subviews as required
UICollectionViewLayoutAttributes *attributes = [collectionView layoutAttributesForSupplementaryElementOfKind:kind atIndexPath:indexPath];
CGRect frame = attributes.frame;
UILabel *label = [[UILabel alloc] initWithFrame: // CGRectMake based on header frame
label.tag = 1;
[header addSubview:label];
// configure label
return header;
}
I have got a Perfect solution:
let footerView = self.collectionView.visibleSupplementaryViews(ofKind: UICollectionView.elementKindSectionFooter)
Now you can access all subview of footerView by using:
footerView[0].subviews[0]
If you have label in your footerView then :
let label: UILabel = footerView[0].subviews[0] as? UILabel ?? UILabel()
Final Step:
label.text = "Successfully Updated Footer."
if let footerView = collectionView.subviews.first(where: {$0 is LoadingFooterCell}) as? LoadingFooterCell {
footerView.isLoading = .loading
}

can NSTreeController setcontent be used with NSXMLDocument?

I'm trying to display the content of a simple plist (xml) file in an outlineview.
Once I have the file data in either an NSXMLDocument or an NSDictionary, is it possible to just use this existing structure to populate the TreeController? All the code examples I can find parse through and reconstruct all the nodes and contents. Isn't this already established in the NSXMLDocument?
thanks
rob
Bindings make this really easy.
You can use a NSTreeController combined with an NSOutlineView and very little code if you use standard bindings.
To make the NSXML objects in the sample application work together with the NSTreeController object, you simply have to add a couple methods to the NSXMLNode class through a category.
https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/NSXML_Concepts/Articles/UsingTreeControllers.html
#import "NSXMLNode+NSXMLNodeAdditions.h"
#implementation NSXMLNode (NSXMLNodeAdditions)
- (NSString *)displayName {
NSString *displayName = [self name];
if (!displayName) {
displayName = [self stringValue];
}
return displayName;
}
- (BOOL)isLeaf {
return [self kind] == NSXMLTextKind ? YES : NO;
}
#end
here are screenshots of the relevant settings for both the NSTreeContoller
and NSOutlineView's TableColumn

Resources