Send a local notification by Xcode command line tool - objective-c

I've to develop a tiny program that send a local notification in on my Mac. This notification must be sent every 30 sec, so I create a command line tool with Xcode and I choose to develop in Objective-C. Xcode shows me the main.m file, in which I wrote the following code:
main.m
#import <Foundation/Foundation.h>
#import "Notification.h"
int main(int argc, const char * argv[]) {
#autoreleasepool {
Notification *notification = [[Notification alloc]init];
[notification startNotification];
}
return 0;
}
where Notification it's a class in which I start a timer and I send the notification, the code is:
Notification.h
#interface Notification : NSObject <NSUserNotificationCenterDelegate>
- (void) startNotification;
#end
Notification.m
#import <Foundation/Foundation.h>
#import <Cocoa/Cocoa.h>
#import "Notification.h"
#implementation Notification : NSObject
- (void) startNotification {
NSTimer *timer = [[NSTimer alloc]init];
if (timer == nil) {
timer = [NSTimer scheduledTimerWithTimeInterval:30.0 target:self selector:#selector(sendNotification) userInfo:nil repeats:YES];
}
}
- (void)sendNotification {
NSUserNotification *notification = [[NSUserNotification alloc]init];
notification.title = #"Prova";
notification.informativeText = #"Ciao Salame";
notification.soundName = NSUserNotificationDefaultSoundName;
[[NSUserNotificationCenter defaultUserNotificationCenter] deliveredNotifications];
NSUserNotificationCenter *center = [NSUserNotificationCenter defaultUserNotificationCenter];
[center scheduleNotification:notification];
[center setDelegate:self];
}
- (void) userNotificationCenter:(NSUserNotificationCenter *)center didActivateNotification:(NSUserNotification *)notification {
NSAlert *alert = [[NSAlert alloc]init];
[alert addButtonWithTitle:#"OK"];
[alert setMessageText:notification.title];
[alert setInformativeText:notification.informativeText];
[alert setAlertStyle:NSWarningAlertStyle];
[alert runModal];
[center removeDeliveredNotification:notification];
}
#end
when I run the app it doesn't calls the selector method and it finish to run, what's wrong in my little program? Can you help me to fix it?
Thank you
AppleScript
I create a tiny AppleScript and it's working quite well, the code I used is the following:
on idle
display notification "Lorem ipsum dolor sit amet" with title "Title"
return 30
end idle
then I created an app from this script and it will be execute at the startup of the system and it's sending me a notification every 30 sec. I'm not sure if it's the right way to do that, but it's working...
LOOK FROM HERE
Objective-C
I've try to update my code as follow:
Notification.m
#import <Foundation/Foundation.h>
#import <Cocoa/Cocoa.h>
#import "Notification.h"
#implementation Notification : NSObject
- (void) startNotification {
NSUserNotification *notification = [[NSUserNotification alloc]init];
notification.title = #"Prova";
notification.informativeText = #"Ciao Salame";
notification.soundName = NSUserNotificationDefaultSoundName;
[[NSUserNotificationCenter defaultUserNotificationCenter] deliveredNotifications];
NSUserNotificationCenter *center = [NSUserNotificationCenter defaultUserNotificationCenter];
[center scheduleNotification:notification];
[center setDelegate:self];
sleep(30);
}
- (void) userNotificationCenter:(NSUserNotificationCenter *)center didActivateNotification:(NSUserNotification *)notification {
NSAlert *alert = [[NSAlert alloc]init];
[alert addButtonWithTitle:#"OK"];
[alert setMessageText:notification.title];
[alert setInformativeText:notification.informativeText];
[alert setAlertStyle:NSWarningAlertStyle];
[alert runModal];
[center removeDeliveredNotification:notification];
}
but it doesn't send me the notification...

The reason is, that your app directly returns after instantiating Notification. Just calling startNotification does not prevent the app from returning. Probably you want to let it run inside a thread in the background.

Related

How do assign a type of file to my app on Mac

I try to assign a file type to my application.
In Info.plist I add:
<key>CFBundleDocumentTypes</key>
<array>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>type</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>icon</string>
<key>CFBundleTypeName</key>
<string>My Project</string>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>LSTypeIsPackage</key>
<false/>
</dict>
</array>
In Main.mm:
....
-(BOOL) application:(NSApplication *)sender openFile:(NSString *)filename {
NSLog(#"Opened by file");
return YES;
}
#end
int main(int argc, char* argv[]) {
[NSApplication sharedApplication];
[[[[Window alloc] init] autorelease] makeMainWindow];
[NSApp run];
return 0;
}
But when I try double click on the my file type, The app only open with the warn: could not be opened, MyApp cannot open file in format. Also the message from NSLog is not called at all.
There are several issues with the code you posted but I was able to get the desired behavior with a few modifications.
I assume this is your window interface and implementation:
#interface Window : NSWindow <NSApplicationDelegate>
- (BOOL)application:(NSApplication *)sender openFile:(NSString *)filename;
#end
#implementation Window
- (BOOL)application:(NSApplication *)sender openFile:(NSString *)filename {
NSLog(#"Opened by file");
return YES;
}
#end
This is extremely odd to be using a window object as an application delegate. Normally, you have a controller object that owns and manages the window and also acts as the application's delegate.
In any case, it's still possible to get... well, functional behavior by changing the main() function to the following:
int main(int argc, const char * argv[]) {
[NSApplication sharedApplication];
Window *window = [[[Window alloc] init] autorelease];
NSApp.delegate = window;
[window makeKeyAndOrderFront:nil];
[NSApp run];
return 0;
}
There are two notable changes. First, your problem was that you didn't set the window instance to be the application delegate. Second, IIRC, you should never call -makeMainWindow directly; rather, that method exists so that you can override it if you wish. If you want to display the window on screen, you call -makeKeyAndOrderFront:.
Opening a file should display the logged line in console (if you're using Xcode 12.5.1, resize the log window if needed to workaround the display bug).
Under manual reference counting, I believe this would leak memory, since no autorelease pool is created, but I didn't see any of the usual warnings in console. Anyway, while this code works, it results in a fairly undesirable scenario. There is no main menu in the app so to quit it you have to use the Dock. The window created is also tiny and has no resizing capabilities, etc.
EDIT: An example project is at https://github.com/NSGod/OpenFile.
The following subclass of NSWindow should allow you to save a file with a unique ‘.jaf’ extension and then reopen the file into the app by double clicking on it. The info.plist is not as critical as I initially thought; I did not alter the one created by Xcode. Most important for this non-Document based app seems to be the calling of NSApplicationDelegate method -(BOOL) application: openFile. The NSApplicationDelegate was added to the NSWindow subclass instead of having a separate AppDelegate as is usually the case. When working correctly you should hear a beep when this method is called after a .jaf file is double-clicked; I couldn’t find the NSLog output. To run the demo in Xcode first create an objc project and delete everything in the ‘main.m’ file and copy/paste the following source code into it. Delete the pre-supplied AppDelegate class to avoid duplicate symbols. In the entitlements set the App Sandbox to NO and set read-only to zero. After the JAF app has been made, use it to save a file to your desktop with the ‘.jaf’ extension. Then make a copy of the app (shown in the Finder) and copy/paste it into the Applications folder. The next step is critical; right click on the file that you just made and use either Get Info or Open With to set the file to always open with your newly made app. At this point you should be able to double click on the xxxx.jaf file and have it open into your app with an audible beep.
#import <Cocoa/Cocoa.h>
#interface Window : NSWindow <NSApplicationDelegate> {
NSTextView *txtView;
}
- (instancetype)initWithContentRect:(NSRect)contentRect styleMask:(NSWindowStyleMask)style backing:(NSBackingStoreType)backingStoreType defer:(BOOL)flag;
-(void) buildMenu;
-(void) openAction;
-(void) saveAction;
#end
#implementation Window
#define _wndW 700
#define _wndH 550
- (BOOL)application:(NSApplication *)sender openFile:(NSString *)filename {
NSLog(#"This comes from JAF : filename = %#.",filename);
NSBeep(); // Listen for this.
NSError *error;
NSURL *url = [NSURL fileURLWithPath:filename];
NSString *fileStr = [[NSString alloc] initWithContentsOfURL:url encoding:NSUTF8StringEncoding error:&error];
if (!fileStr) {
NSLog(#"Unable to open file %#", error);
} else {
[txtView setString:fileStr];
}
return YES;
}
-(void) buildMenu {
// **** Menu Bar **** //
NSMenu *menubar = [NSMenu new];
[NSApp setMainMenu:menubar];
// **** App Menu **** //
NSMenuItem *appMenuItem = [NSMenuItem new];
NSMenu *appMenu = [NSMenu new];
[appMenu addItemWithTitle: #"Quit" action:#selector(terminate:) keyEquivalent:#"q"];
[appMenuItem setSubmenu:appMenu];
[menubar addItem:appMenuItem];
}
-(void) openAction {
NSOpenPanel *op = [NSOpenPanel openPanel];
[op setAllowedFileTypes:[NSArray arrayWithObjects: #"jaf", #"txt", nil]];
[op beginSheetModalForWindow: self completionHandler: ^(NSInteger returnCode) {
if (returnCode == NSModalResponseOK) {
NSURL *url = [op URL];
NSError *error;
NSString *fileStr = [[NSString alloc] initWithContentsOfURL:url encoding:NSUTF8StringEncoding error:&error];
if (!fileStr) {
NSLog(#"Unable to open file %#", error);
} else {
[self->txtView setString:fileStr];
}
}
}];
}
-(void) saveAction {
NSSavePanel *sp = [NSSavePanel savePanel];
[sp setTitle:#"Save contents to file"];
[sp setAllowedFileTypes:[NSArray arrayWithObjects: #"jaf", nil]];
[sp setNameFieldStringValue: #".jaf"];
[sp beginSheetModalForWindow: self completionHandler: ^(NSInteger returnCode) {
if (returnCode == NSModalResponseOK) {
NSURL *url = [sp URL];
NSString *viewStr = [[self->txtView textStorage] string];
NSError *err;
BOOL fileSaved = [viewStr writeToURL:url atomically:YES encoding:NSUTF8StringEncoding error:&err];
if (!fileSaved) { NSLog(#"Unable to save file due to error: %#", err);}
}
}];
}
- (instancetype)initWithContentRect:(NSRect)contentRect styleMask:(NSWindowStyleMask)style backing:(NSBackingStoreType)backingStoreType defer:(BOOL)flag {
self = [super initWithContentRect:NSMakeRect(0, 0, _wndW, _wndH) styleMask:NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskResizable backing:NSBackingStoreBuffered defer:NO];
[self setTitle: #"Test window"];
[self center];
[self makeKeyAndOrderFront: nil];
// ****** NSTextView with Scroll ****** //
NSScrollView *scrlView = [[NSScrollView alloc] initWithFrame:NSMakeRect( 10, 10, _wndW - 20, _wndH - 80 )];
[[self contentView] addSubview:scrlView];
[scrlView setHasVerticalScroller: YES];
[scrlView setAutoresizingMask: NSViewWidthSizable | NSViewHeightSizable ];
txtView = [[NSTextView alloc] initWithFrame:NSMakeRect( 0, 0, _wndW - 20, _wndH - 80 )];
[scrlView setDocumentView: txtView];
// **** Open Button **** //
NSButton *openBtn =[[NSButton alloc]initWithFrame:NSMakeRect( 30, _wndH - 50, 95, 30 )];
[openBtn setBezelStyle:NSBezelStyleRounded ];
[openBtn setTitle: #"Open"];
[openBtn setAutoresizingMask: NSViewMinYMargin];
[openBtn setAction: #selector (openAction)];
[[self contentView] addSubview: openBtn];
// **** Save Button **** //
NSButton *saveBtn =[[NSButton alloc]initWithFrame:NSMakeRect( 130, _wndH - 50, 95, 30 )];
[saveBtn setBezelStyle:NSBezelStyleRounded ];
[saveBtn setTitle: #"Save"];
[saveBtn setAutoresizingMask: NSViewMinYMargin];
[saveBtn setAction: #selector (saveAction)];
[[self contentView] addSubview: saveBtn];
return self;
}
- (BOOL)windowShouldClose:(id)sender {
[NSApp terminate:sender];
return YES;
}
#end
int main() {
NSApplication *application = [NSApplication sharedApplication];
Window *window = [[Window alloc]init];
[window buildMenu];
[application setDelegate:window];
[application activateIgnoringOtherApps:YES];
[NSApp run];
return 0;
}

Delegating UIAlertView to another class / file? Not working?

so I'm new to iOS development and I'm trying to delegate the button click event to another class. Whenever I click a button on the alert, the app crashes and I get an error saying Thread_1 EXC_BAD_ACCESS.
This is my code.
// theDelegateTester.h
#import <UIKit/UIKit.h>
#interface theDelegateTester : UIResponder <UIAlertViewDelegate>
- (void) alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex;
#end
Implementation..
// theDelegateTester.m
#import "theDelegateTester.h"
#implementation theDelegateTester
- (void) alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
NSLog(#"Delegated");
}
#end
And here's the implementation for my view file..
#import "appleTutorialViewController.h"
#import "theDelegateTester.h"
#interface appleTutorialViewController ()
- (IBAction)tapReceived:(id)sender;
#end
#implementation appleTutorialViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
- (void)viewDidUnload
{
// Release any retained subviews of the main view.
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);
}
- (IBAction)tapReceived:(id)sender {
theDelegateTester *newTester = [[theDelegateTester alloc] init];
UIAlertView *myAlert = [[UIAlertView alloc] initWithTitle:#"Alert!" message:#"This is a delegated alert" delegate:newTester cancelButtonTitle:#"Close" otherButtonTitles:#"Cool!", nil];
[myAlert show];
}
#end
First of all, you should always start your class names with a capital letter, so you can differentiate between classes and instances or methods easily.
And you probably leak the delegate class. You should declare a strong/retained property TheDelegateTester *myDelegate in your view controller. Then in tapReceived: something like this:
- (IBAction)tapReceived:(id)sender {
if (!self.myDelegate) {
TheDelegateTester *del = [[TheDelegateTester alloc] init];
self.myDelegate = del;
[del release];
}
UIAlertView *myAlert = [[UIAlertView alloc] initWithTitle:#"Alert!" message:#"This is a delegated alert" delegate:newTester cancelButtonTitle:#"Close" otherButtonTitles:#"Cool!", nil];
[myAlert show];
[myAlert release];
}

Best Technique for Replacing Delegate Methods with Blocks

I'm looking to create a category to replace delegate methods with callbacks blocks for a lot of the simple iOS APIs. Similar to the sendAsyc block on NSURLConnection. There are 2 techniques that are leak free and seem to work fine. What are the pros/cons about each? Is there a better way?
Option 1. Use a category to implement the delegate's callback method on NSObject with the external callback block scoped.
// Add category on NSObject to respond to the delegate
#interface NSObject(BlocksDelegate)
- (void) alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex;
#end
#implementation NSObject(BlocksDelegate)
- (void) alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
// Self is scoped to the block that was copied
void(^callback)(NSInteger) = (id)self;
// Call the callback passed if
callback(buttonIndex);
[self release];
}
#end
// Alert View Category
#implementation UIAlertView (BlocksDelegate)
+ (id) alertWithTitle:(NSString*)title
message:(NSString*)message
clickedBlock:(void(^)(NSInteger))buttonIndexClickedBlock
cancelButtonTitle:(NSString*)cancelButtonTitle
otherButtonTitles:(NSString*)otherButtonTitles
{
// Copy block passed in to the Heap and will stay alive with the UIAlertView
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:title
message:message
delegate:[buttonIndexClickedBlock copy]
cancelButtonTitle:cancelButtonTitle
otherButtonTitles:otherButtonTitles, nil];
// Display the alert
[alert show];
// Autorelease the alert
return [alert autorelease];
}
#end
This adds a lot of methods on the NSObject and seems like it could cause issues with any other class trying to use the standard delegate method. But it keeps the block alive with the object and returns the callback without any leaks that I've found.
Option 2. Create an light-weight class to contain the block, dynamicly associate it with the class so it will stay in the heap and remove it when the callback is complete.
// Generic Block Delegate
#interface __DelegateBlock:NSObject
typedef void (^HeapBlock)(NSInteger);
#property (nonatomic, copy) HeapBlock callbackBlock;
#end
#implementation __DelegateBlock
#synthesize callbackBlock;
- (id) initWithBlock:(void(^)(NSInteger))callback
{
// Init and copy Callback Block to the heap (#see accessor)
if (self = [super init])
[self setCallbackBlock:callback];
return [self autorelease];
}
- (void) dealloc
{
// Release the block
[callbackBlock release], callbackBlock = nil;
[super dealloc];
}
- (void) alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
// Return the result to the callback
callbackBlock(buttonIndex);
// Detach the block delegate, will decrement retain count
SEL key = #selector(alertWithTitle:message:clickedBlock:cancelButtonTitle:otherButtonTitles:);
objc_setAssociatedObject(alertView, key, nil, OBJC_ASSOCIATION_RETAIN);
key = nil;
// Release the Alert
[alertView release];
}
#end
#implementation UIAlertView (BlocksDelegate)
+ (id) alertWithTitle:(NSString*)title
message:(NSString*)message
clickedBlock:(void(^)(NSInteger))buttonIndexClickedBlock
cancelButtonTitle:(NSString*)cancelButtonTitle
otherButtonTitles:(NSString*)otherButtonTitles
{
// Create class to hold delegatee and copy block to heap
DelegateBlock *delegatee = [[__DelegateBlock alloc] initWithBlock:buttonIndexClickedBlock];
[[delegatee retain] autorelease];
// Create delegater
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:title
message:message
delegate:delegatee
cancelButtonTitle:cancelButtonTitle
otherButtonTitles:otherButtonTitles, nil];
// Attach the Delegate Block class to the Alert View, increase the retain count
objc_setAssociatedObject(alert, _cmd, delegatee, OBJC_ASSOCIATION_RETAIN);
// Display the alert
[alert show];
return alert;
}
#end
I like that this doesn't add anything on top of NSObject and things are a little more separated. It's attaching to the instance via the address of the function.
I had a similar problem and chose your option 2, but with the 2 small additions:
Explicitly marking the delegate it implements like this:
#interface __DelegateBlock:NSObject <BlocksDelegate>
Check to ensure the callback is not nil before calling:
if (callbackBlock != nil) {
callbackBlock(buttonIndex);
}
Here's what I did:
typedef void(^EmptyBlockType)();
#interface YUYesNoListener : NSObject <UIAlertViewDelegate>
#property (nonatomic, retain) EmptyBlockType yesBlock;
#property (nonatomic, retain) EmptyBlockType noBlock;
+ (void) yesNoWithTitle:(NSString*)title message:(NSString*)message yesBlock:(EmptyBlockType)yesBlock noBlock:(EmptyBlockType)noBlock;
#end
#implementation YUYesNoListener
#synthesize yesBlock = _yesBlock;
#synthesize noBlock = _noBlock;
- (id) initWithYesBlock:(EmptyBlockType)yesBlock noBlock:(EmptyBlockType)noBlock
{
self = [super init];
if (self)
{
self.yesBlock = [[yesBlock copy] autorelease];
self.noBlock = [[noBlock copy] autorelease];
}
return self;
}
- (void) alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
if (buttonIndex == 0 && self.noBlock)
self.noBlock();
else if (buttonIndex == 1 && self.yesBlock)
self.yesBlock();
[_yesBlock release];
[_noBlock release];
[alertView release];
[self release];
}
- (void) alertViewCancel:(UIAlertView *)alertView
{
if (self.noBlock)
self.noBlock();
[_yesBlock release];
[_noBlock release];
[alertView release];
[self release];
}
+ (void) yesNoWithTitle:(NSString*)title message:(NSString*)message yesBlock:(EmptyBlockType)yesBlock noBlock:(EmptyBlockType)noBlock
{
YUYesNoListener* yesNoListener = [[YUYesNoListener alloc] initWithYesBlock:yesBlock noBlock:noBlock];
[[[UIAlertView alloc] initWithTitle:title message:message delegate:yesNoListener cancelButtonTitle:#"No" otherButtonTitles:#"Yes", nil] show];
}
#end

Objective C Blocks in an Array Passed To Subclass of UIAlertView

(A working solution, based on the responses, is provided at the end of this post.)
I thought this would be a tidy way to handle the callbacks that a particular alert view needs to address, so I don't have a single delegate method filtering all of the alert button presses. Here is the code:
#import "LSAlertView.h"
#implementation LSAlertView
- (id) initWithTitle:(NSString *)title
message:(NSString *)message
actionBlocks:(NSArray*)_actionBlocks
cancelButtonTitle:(NSString *)cancelButtonTitle
otherButtonTitles:(NSString *)otherButtonTitles, ...
{
self = [super initWithTitle:title
message:message
delegate:self
cancelButtonTitle:cancelButtonTitle
otherButtonTitles:otherButtonTitles,nil];
if (self) {
self.cancelButtonIndex = 0;
actionBlocks = [_actionBlocks retain];
[self show];
}
return self;
}
- (void) dealloc {
[actionBlocks release];
[super dealloc];
}
- (void) alertView:(UIAlertView *)alertView
clickedButtonAtIndex:(NSInteger)buttonIndex
{
void (^action)(void) = [actionBlocks objectAtIndex:buttonIndex];
action();
}
#end
This works fine for two buttons set up like this:
- (void) restartSearches {
NSArray *actionBlocks = [NSArray arrayWithObjects:
^{NSLog(#"Cancel Button Selected");},
^{NSLog(#"Delete Button Selected");},
nil];
alertDeletingSearches = [[LSAlertView alloc]
initWithTitle:#"You Are About To Delete Your Current Searches"
message:#"Select Delete to Continue"
actionBlocks:actionBlocks
cancelButtonTitle:#"Cancel"
otherButtonTitles:#"Delete", nil];
[alertDeletingSearches release];
}
But as soon as I add some useful calls in one of the blocks, like this
- (void) restartSearches {
NSArray *actionBlocks = [NSArray arrayWithObjects:
^{NSLog(#"Cancel Button Selected");},
^{
[mapController.theMap removeAnnotations:mapController.theMap.annotations];
[dataInterface deleteDB];
[[NSNotificationCenter defaultCenter]
postNotificationName:#"changeToFavorites"
object:nil];
NSLog(#"Delete Button Selected");
},
nil];
alertDeletingSearches = [[LSAlertView alloc]
initWithTitle:#"You Are About To Delete Your Current Searches"
message:#"Select Delete to Continue" actionBlocks:actionBlocks
cancelButtonTitle:#"Cancel"
otherButtonTitles:#"Delete", nil];
[alertDeletingSearches release];
}
it freezes, and I get a EXC_BAD_ACCESS error.
Am I doing something fundamentally wrong, or is there a minor error in my logic?
UPDATE
Handled the variadic problem problem using Firoze's suggestion below. (Follows the examples given at Numbergrinder)
- (id) initWithTitle:(NSString *)title message:(NSString *)message actionBlocks:(NSArray*)_actionBlocks cancelButtonTitle:(NSString *)cancelButtonTitle otherButtonTitles:(NSString *)otherButtonTitles, ... {
self = [super initWithTitle:title message:message delegate:self cancelButtonTitle:cancelButtonTitle otherButtonTitles:otherButtonTitles, nil];
if (self) {
va_list args;
va_start(args, otherButtonTitles);
NSString* buttonTitle;
while ((buttonTitle = va_arg(args, NSString *))) {
[super addButtonWithTitle:buttonTitle];
}
self.cancelButtonIndex = 0;
actionBlocks = [_actionBlocks retain];
[self show];
}
return self;
}
Here is the header file:
#interface LSAlertView : UIAlertView <UIAlertViewDelegate> {
NSArray *actionBlocks;
}
- (id) initWithTitle:(NSString *)title message:(NSString *)message actionBlocks:(NSArray*)_actionBlocks cancelButtonTitle:(NSString *)cancelButtonTitle otherButtonTitles:(NSString *)otherButtonTitles, ...;
#end
So I see a couple of issues with this.
One is that you need to copy those blocks as you put them in the array. Those blocks are created on the stack. If you want to pass them to your alert view and you expect the alert view to hold onto them for later use, you need to copy them to the heap first.
So something like this should work:
NSArray *actionBlocks = [NSArray arrayWithObjects:
[[^{NSLog(#"Cancel Button Selected");} copy] autorelease],
[[^{
[mapController.theMap removeAnnotations:mapController.theMap.annotations];
[dataInterface deleteDB];
[[NSNotificationCenter defaultCenter] postNotificationName:#"changeToFavorites" object:nil];
NSLog(#"Delete Button Selected");
} copy] autorelease]
, nil];
Note the [^someBlock copy] around each block literal there. That should solve one issue.
The other issue, to which I don't know the answer, is that this is a variadic method (takes a variable number of arguments). I don't know of a way in a variadic method to turn around and call another variadic method (the UIAlertView initializer), unless you have a variation of the second method that takes a va_list. This is the same issue we have in C, inherited in Objective C as far as I understand it.
I think you haven't run into that yet because you haven't tried enough buttons for that.
EDIT
Thinking about this further, I guess you could get around the second issue by iterating through the varargs and then calling [self addButtonWithTitle:arg] for each of them.
You might find Lambda Alert useful:
LambdaAlert *alert = [[LambdaAlert alloc]
initWithTitle:#"Test Alert"
message:#"See if the thing works."];
[alert addButtonWithTitle:#"Foo" block:^{ NSLog(#"Foo"); }];
[alert addButtonWithTitle:#"Bar" block:^{ NSLog(#"Bar"); }];
[alert addButtonWithTitle:#"Cancel" block:NULL];
[alert show];
And:
LambdaSheet *sheet = [[LambdaSheet alloc] initWithTitle:#"Action Sheet"];
[sheet addButtonWithTitle:#"Miles" block:^{ NSLog(#"Trumpet"); }];
[sheet addButtonWithTitle:#"Trane" block:^{ NSLog(#"Saxophone"); }];
[sheet addDestructiveButtonWithTitle:#"Monk" block:^{ NSLog(#"Piano"); }];
[sheet addCancelButtonWithTitle:#"Back to the Head"];
[sheet showInView:window];
Static library, easy to include with your project using an Xcode workspace.

UIAlertView showing up only after it's dismissed

I've been trying to figure this out for 2 days now, and before anyone posts another stackoverflow question, I've read them all and none of them cover my problem exactly:
I have a CoreData app that updates dynamically. Now during the update I want an UIAlertView to pop up saying that an update is being downloaded.
So here's the important code:
AppDelegate:
- (void)applicationDidBecomeActive:(UIApplication *)application
{
[myUpdater checkForUpdatesInContext:self.managedObjectContext];
}
_
Updater Class:
- (void)checkForUpdatesInContext:(NSManagedObjectContext *)myManagedObjectContext
{
[self loadUpdateTime];
NSLog(#"Update start");
NSDate *now = [NSDate dateWithTimeIntervalSinceNow:[[NSTimeZone localTimeZone] secondsFromGMT]];
if ([now timeIntervalSinceDate:updateTime] < UPDATE_TIME_INTERVAL)
{
return;
}
[self showAlertViewWithTitle:#"Update"];
... //updating process
[self.alertView dismissWithClickedButtonIndex:0 animated:YES];
NSLog (#"Update done");
}
- (void) showAlertViewWithTitle:(NSString *)title
{
self.alertView = [[UIAlertView alloc] initWithTitle:title message:#"Daten werden aktualisiert..." delegate:self cancelButtonTitle:nil otherButtonTitles:nil];
... //design the alertView
[self.alertView show];
NSLog (#"AlertView shows");
}
So here is what happens when I run this:
Launch image shows
NSLog "Update starts" fires
NSLog "AlertView shows" fires
Screen dims but no AlertView is shown
Update is running
NSLog "Update done" fires
Launch image goes away and TabBarController shows up
UIAlertView shows up and is dismissed right away and the dimmed screen returns to normal
What I would like to have happen:
Launch image
TabBarController shows up
Screen dims and UIAlertView shows
Update is running
UIAlertView gets dismissed and dimmed screen returns to normal
I know it's something with the UI Thread and the main Thread and stuff.. But I tried every combination it seems but still not the expected result. Please help :)
EDIT:
HighlightsViewController Class:
- (void)viewDidLoad
{
[super viewDidLoad];
self.updater = [[Updater alloc] init];
[updater checkForUpdatesInContext:self.managedObjectContext];
... // other setup stuff nothing worth mentioning
}
Is this the right place to call [super viewDidLoad]? Because it still doesn't work like this, still the update is being done while the Launch Image is showing on the screen. :-(( I'm about to give this one up..
Here you go, in this prototype things work exactly how you want them to.
Header:
#import <UIKit/UIKit.h>
#interface AlertViewProtoViewController : UIViewController
{
}
- (void) showAlertViewWithTitle:(NSString *)title;
- (void) checkForUpdatesInContext;
- (void) update;
- (void)someMethod;
- (void)someOtherMethod;
#end
#import "AlertViewProtoViewController.h"
Class:
#implementation AlertViewProtoViewController
UIAlertView *alertView;
bool updateDone;
UILabel *test;
bool timershizzle;
#pragma mark - View lifecycle
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.view.backgroundColor = [UIColor yellowColor];
UILabel *test = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 500, 500)];
test.backgroundColor = [UIColor blueColor];
[self.view addSubview:test];
[self performSelector:#selector(checkForUpdatesInContext) withObject:nil afterDelay:0.0];
}
- (void)update
{
//NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; //commented for auto ref counting
NSLog(#"update start");
//your update stuff
NSLog(#"update end");
updateDone = YES;
//[pool release];
}
- (void)checkForUpdatesInContext//:(NSManagedObjectContext *)myManagedObjectContext
{
//[self loadUpdateTime];
NSLog(#"Update start");
NSDate *now = [NSDate dateWithTimeIntervalSinceNow:[[NSTimeZone localTimeZone] secondsFromGMT]];
// if ([now timeIntervalSinceDate:updateTime] < UPDATE_TIME_INTERVAL)
// {
// return;
// }
[self showAlertViewWithTitle:#"Update"];
//[self setManagedObjectContext:myManagedObjectContext];
[self performSelector:#selector(someMethod) withObject:nil afterDelay:0.0];
[self performSelector:#selector(someOtherMethod) withObject:nil afterDelay:0.0];
}
-(void)someOtherMethod
{
while (!updateDone) {
// NSLog(#"waiting...");
}
[alertView dismissWithClickedButtonIndex:0 animated:YES];
NSLog (#"Update done");
self.view.backgroundColor = [UIColor greenColor];
}
-(void)someMethod
{
[self performSelectorInBackground:#selector(update) withObject:nil];
}
- (void) showAlertViewWithTitle:(NSString *)title
{
alertView = [[UIAlertView alloc] initWithTitle:title message:#"Daten werden aktualisiert..." delegate:self cancelButtonTitle:nil otherButtonTitles:nil];
alertView.frame = CGRectMake(100, 100, 200, 200);
alertView.backgroundColor = [UIColor whiteColor];
[self.view addSubview:alertView];
[self.view setNeedsDisplay];
NSLog (#"AlertView shows");
}
#end
You should adjust were needed for your own purposes but it works.
You are starting a background thread and then dismissing the alert immediately. I would suggest that you might use an NSNotification, posted from the background task, and received in whichever controller starts the alert, triggering a method that dismissed the alert.
I find the UIAlertView interface unsuitable for this type of user notice, and prefer to use a semi-transparent overlay view with a UIActivityIndicatorView, plus an informing message for the user.
You are doing a:
- (void)applicationDidBecomeActive:(UIApplication *)application
Isn't it so that the alertview you want to show needs a view to be loaded which isn't active yet at this point? See: http://developer.apple.com/library/ios/#documentation/uikit/reference/UIAlertView_Class/UIAlertView/UIAlertView.html
Similar question? UIAlertView starts to show, screen dims, but it doesn't pop up until it's too late!