iPhone: How to detect if an EKEvent instance can be modified? - iphone

While working with the EventKit on iPhone I noticed that some events can exist which cannot be modified. Examples I encountered so far are birthdays and events synced with CalDAV. When you view the event's details in the standard built-in calendar app on iPhone the "Edit" button in the top-right corner is not visible in these cases, where it would be visible when viewing "normal" events.
I've searched everywhere, read all documentation there is but I simply can't find anything that tells me how to detect this behavior! I can only detect it afterwards:
edit an event's title
save it to the event store
check the event's title, if it has not changed it is not editable!
I am looking for a way that I can detect the non-editable behavior of an event beforehand. I know this is possible because I've seen other calendar apps implement this correctly.

Ok it appears as if the SDK doesn't provide me with anything I can use to check if an EKEvent is read-only. I created a workaround by creating a category that adds an "isReadOnly" method to all EKEvent instances.
EKEvent+ReadOnlyCheck.h
#interface EKEvent(ReadOnlyCheck)
- (BOOL) isReadOnly;
#end`
EKEvent+ReadOnlyCheck.m
#import "EKEvent+ReadOnlyCheck.h"
#implementation EKEvent(ReadOnlyCheck)
- (BOOL) isReadOnly {
BOOL readOnly;
NSString *originalTitle = [self.title retain];
NSString *someRandomTitle = [NSString stringWithFormat:#"%i", arc4random()];
self.title = someRandomTitle;
readOnly = [originalTitle isEqualToString:self.title];
self.title = originalTitle;
[originalTitle release];
return readOnly;
}
#end
When the above files are in place I can simply call isReadOnly on the EKEvent of my choice.
#import "EKEvent+ReadOnlyCheck.h"
...
if ([event isReadOnly]) {
// Do your thing
}
...

I haven't worked with Event Kit yet, but from the documentation it seems that editability is a property of a calendar, not of an event. event.calendar gets you the event's calendar, and calendar.allowsContentModifications tells you if the calendar is read-only or read-write.

try allowsEditing property of EKEventViewController before displaying the view.

Yes. It is possible. The code would look like following :
Try relating to code by logging the output of objects I use with editable/non editable events and you will understand the working:)
EKEventViewController *controller = [[EKEventViewController alloc] init];
controller.event = myEvent; /*myEvent is of type EKEvent*/
if(controller.navigationItem.leftBarButtonItem != NULL)
{
/*Event is Editable, Your code here*/
}

I think adding this category method for EKEvent handles all cases where events are not editable:
- (BOOL)isReadOnly {
if (self.calendar.allowsContentModifications == NO) return YES;
if (self.organizer && [self.organizer isCurrentUser] == NO) return YES;
return NO;
}

Related

Adding Speech to custom UIMenuController

I created a custom UIMenuController in a UIWebView but it seems to get rid of the "Speak Selection" option in the UIMenuController after that. The speak selection option is turned on in Preferences on all test devices and it appears in other apps, including non-Apple apps. Is there an accessibility service or part of the sharedMenuController that I can call to get this item?
UIMenuItem *copyMenuItem = [[UIMenuItem alloc] initWithTitle:NSLocalizedString(#"Copy", #"Copy menu item") action:#selector(myappCopy:)];
UIMenuItem *highlightMenuItem = [[UIMenuItem alloc] initWithTitle:NSLocalizedString(#"Highlight", #"Highlight menu option") action:#selector(myappHighlight:)];
UIMenuItem *unhighlightMenuItem = [[UIMenuItem alloc] initWithTitle:NSLocalizedString(#"Remove Highlight", #"Remove Highlight menu option")
action:#selector(myappRemoveHighlight:)];
UIMenuItem *noteMenuItem = [[UIMenuItem alloc] initWithTitle:NSLocalizedString(#"Note", #"Note menu options") action:#selector(myappNote:)];
[UIMenuController sharedMenuController].menuItems = [NSArray arrayWithObjects:copyMenuItem, highlightMenuItem, unhighlightMenuItem, noteMenuItem, nil];
[copyMenuItem release];
[highlightMenuItem release];
[unhighlightMenuItem release];
[noteMenuItem release];
I even tried to parse the existing shared menu items at the start, but I don't see anything dumped in the log. The method is getting called on app launch.
Tried this at top of method:
for (UIMenuItem *menuItem in [UIMenuController sharedMenuController].menuItems) {
NSLog(#"title: %#", menuItem.title);
NSLog(#"action: %#", menuItem.action);
}
Any help is much appreciated! Thanks - Eric
I've found some interesting things with this bug. Basically, when speak selection is enabled, after you make the first selection the UIMenuController is emptied of menuItems. The solution, though hacky, is simple.
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender {
NSString *selectorString = NSStringFromSelector(action);
BOOL isAccessibilitySelector = [selectorString isEqualToString:#"_accessibilitySpeak:"] || [selectorString isEqualToString:#"_accessibilityPauseSpeaking:"];
if (isAccessibilitySelector && [super canPerformAction:action withSender:sender]) {
//(re)add menuItems to UIMenuController
return YES;
}
return NO;
}
I should note that you must re-add the menuItems after their canPerformAction...() has been called.
I have submitted this as radar:12931434. Update: DUP'ed to 13060693.
Some of the UIMenuController items can be found in UIResponder.h in UIKit framework.
#interface NSObject(UIResponderStandardEditActions) // these methods are not implemented in NSObject
- (void)cut:(id)sender __OSX_AVAILABLE_STARTING(__MAC_NA,__IPHONE_3_0);
- (void)copy:(id)sender __OSX_AVAILABLE_STARTING(__MAC_NA,__IPHONE_3_0);
- (void)paste:(id)sender __OSX_AVAILABLE_STARTING(__MAC_NA,__IPHONE_3_0);
- (void)select:(id)sender __OSX_AVAILABLE_STARTING(__MAC_NA,__IPHONE_3_0);
- (void)selectAll:(id)sender __OSX_AVAILABLE_STARTING(__MAC_NA,__IPHONE_3_0);
- (void)delete:(id)sender __OSX_AVAILABLE_STARTING(__MAC_NA,__IPHONE_3_2);
- (void)makeTextWritingDirectionLeftToRight:(id)sender __OSX_AVAILABLE_STARTING(__MAC_NA,__IPHONE_5_0);
- (void)makeTextWritingDirectionRightToLeft:(id)sender __OSX_AVAILABLE_STARTING(__MAC_NA,__IPHONE_5_0);
#end
But there is no speak text option there. It turns out if you override "canPerformAction: WithSelector:", within a subclass of your UIWebView or UITextField as listed below, you will also get a listing of all of the actions sent to self including the UIMenuController options.
// Override
- (BOOL) canPerformAction:(SEL)action withSender:(id)sender
{
NSLog(#"%#",NSStringFromSelector(action));
//if you are customizing your menu, return NO except for your specific selectors
return YES;
}
You'll find several methods that may interest you, including _accessibilitySpeak: and _accessibilityPauseSpeaking: and _define: (please note these three selectors are iOS 5 only). The underscore means that they are private, so also keep in mind that you can't directly call them with the classic [class selector] syntax.
Remember, these are system menuItems, which means Apple will stick them in front of any menu items you add, often leaving your menu items in a second layer accessed by tapping the > arrow. If you want to control the order in which the items are display, and/or mix Apple's system items with your items, you will need to create custom menu items for these actions that call a method in your class like this:
- (void) myAppSpeak: (UIMenuController*) sender
{
[super performSelector:#selector(_accessibilitySpeak:)];
}
Keep in mind that these methods need to be implemented in a subclass of a class that implements these already, such as a sub class of UIWebView....not a subclass of UIWebViewController.
Then inside the controller, or wherever you build your UIMenuController, create the custom button that calls this method. Be sure if you are in a web view, you are referencing an object of type of your subclass, and not the generic webview. Otherwise, it won't work.
UIMenuItem *speakMenuItem = [[UIMenuItem alloc] initWithTitle:#"Speak" action:#selector(myAppSpeak:)];
[UIMenuController sharedMenuController].menuItems = [NSArray arrayWithObjects:speakMenuItem, etc. etc., nil];
Even though you are adding it to the your menu items, it will not appear unless you return YES for the selector in your canPerformAction: WithSelector: in your subclass of your web view or text field. So feel free to add items here that may be circumstantial otherwise. You can use logic in your subclassed view to sort that out.

I'm having issues inputing a name if applicable

I have a function here that upon completing a single round, if your score is higher than either a default score entry or a newly placed high score then it will swap its data with your data and push everything else down. removing the last entry from the list. currently this is just one exchange and for functions sake I'm going to hard code it and then refactor it later.
My main problem is that when I set up a text input view to capture the players name execution continues immediately without the players input and crashes the game. I commented out the line that sets the text because I have a default value in place just in case any attempt that I try to make fails. How can I get Execution to wait for a moment while input is taken? Would I have to set up a delegate method? If so I'm still a bit confused by delegates. I could set it up to work but I don't understand it, so I wouldn't be able to do any other special custom tasks with it. I've worked on it for a while and got no further...
-(void)saveData:(ScoreKeep *)stats{
NSMutableDictionary *swap = [[NSMutableDictionary alloc]init];//used for swaping entries
NSString *filePath = [self pathOfFile];
NSLog(#"Writing to %#", filePath);
if ([[NSFileManager defaultManager]fileExistsAtPath:filePath]) {
NSLog(#"Loading previous dictionary to save...");
dataDictionary = [NSMutableDictionary dictionaryWithContentsOfFile:filePath];
if ([dataDictionary objectForKey:#"1"]) {
NSMutableDictionary *highScore = [dataDictionary objectForKey:#"1"];
if ([stats.score intValue] > [[highScore objectForKey:#"SCORE"] intValue]) {
NSLog(#"You Win! score: %# highscore: %#", stats.score,[NSNumber numberWithInt:[[highScore objectForKey:#"SCORE"] intValue]] );
stats = [[ScoreKeep alloc] initWithNibName:#"Scorekeep" bundle:nil];
NSLog(#"Setting up name entry");
[self.view addSubview:stats.view]; //New view is added so that the player can input data(Assume it is complete);
//stats.nameTag = setName.nameTag;//This line is executed before the new view is dismissed causing an error to occur
[stats setupDictionary]; // It just goes down hill from here if the previous line is uncommented
[dataDictionary setObject:stats.sComponents forKey:#"1"];
}else {
NSLog(#"You Lose: %# highscore: %#", stats.score,[NSNumber numberWithInt:[[highScore objectForKey:#"SCORE"] intValue]] );
}
NSLog(#"Got first place entry");
}else {
NSLog(#"Initilizing Score");
}
}else{
NSLog(#"Creating new dictionary to save...");
dataDictionary = [[NSMutableDictionary alloc]init];
}
[dataDictionary writeToFile:filePath atomically:YES];
}
Help would greatly be appreciated. If more information is needed I'd be happy to provide.
by the way ScoreKeep is an object that contains a dictionary and a function to create a dictionary such that it can set any values I need and package them into sComponents(the dictionary to be entered into the main savefile)
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
#class omphalosUtility;
#pragma mark -
#pragma mark Saving data
#pragma mark -
static inline void poop(){
NSLog(#"POOP");
}
I'm going to try making a utility file that works independently of the app so that I Can update files and perform other universal operations such as saving when needed. Its a step in a direction that i'd like to take.
If i get it right, (The code is really nasty, man...) your problem is that you are trying to present a View Controller with the wrong way.
Correct me if i'm wrong, is ScoreKeep is a ViewController? if so, you have to name it properly. that's for a start.
Second, you cant present another view controller only by adding its "view" property to the current view controller's View Hierarchy. that way the view will not respond properly to the events.
the correct way to to what you'r trying to do is by presenting the ScoreKeep ViewController modally.
there is no other right way to do this without using delegation. you will have to acquire this technique.
Your view controller that responsible for getting the name from the user need to have a way to tell it's master view controller that the user entered a name. and that is achieved through delegation.
What you should do:
Basically you create a protocol called something like "NamePrompterViewControllerDelegate"
that will have at least one method that will be called when the user will done entering his name.
Your ScoreKeepViewController should have an instance variable that implemented the protocol (Look at the apple documentation on protocols for assistance)
Your main view controller (the one that contains the method you added) then should implement the protocol you created, and set itself as the delegate of ScoreKeep like that:
stats = [[ScoreKeep alloc] initWithNibName:#"Scorekeep" bundle:nil];
stats.delegate = self;
For more info on presenting and dismissing ViewControllers modally you should read the documentation at Apple Documentation
I hope i helped you, there is just a lot to cover and it hardly can be done by writing an answer.
Feel free to ask more for clearance.

How to code a "numbers pad" like Call application

how can I do something like this in an iPhone app?
I need to input a number but I want something like this, not a simple UITextField.
How?
Thanks!
I Agree with Kevin. But if you decide to implement your own keyboard-like pad you may have to lose all those nice features provided by the original iOS keyboard.
You will have to create a custom UIView if you want it to look like what you sent. Basically add a set of subviews (UIButtons) for each control. Then create a delegate for the custom UIView that will notify of value changes. For example, here is some rough code to get you started:
// CustomNumbersView.m
- (void)button1DidClick:(id)sender
{
[self.delegate customNumbersView:self didSelectKeyWithValue:#"1"];
}
- (void)button2DidClick:(id)sender
{
[self.delegate customNumbersView:self didSelectKeyWithValue:#"2"];
}
// MainViewController.m
- (void)viewDidLoad
{
[super viewDidLoad];
CustomNumbersView *customNubmersView = [[CustomNumbersView alloc] initWithFrame:...];
customNumbersView.delegate = self;
}
- (void)customNumbersView:(CustomNumbersView *)customNumbersView didSelectKeyWithValue:(NSString *)value
{
self.mainTextField.text = [NSString stringWithFormat:#"%#%#", self.mainTextField.text, value];
}
As I need one in several situations in my programs, I wrote a delegate-driven KeyPad.
I've solved with new feature
UITextField.keyboardType = UIKeyboardTypeDecimalPad;

iPhone SDK: Can I nab in instance variable from one view and access it in another?

I'm pretty new to OOP in general and just really started working with Obj-c a few months back. So, please be gentle! I appreciate your help in advance. Now to the question!
I have 3 Text Fields where a user inputs name, phone, and email.
I collect and place them in a label using an NSString like this [this is the one for the name]:
- (IBAction)changeGreeting:(id)sender {
self.name = textInput.text;
NSString *nameString = name;
if([nameString length] == 0) {
nameString = #"I Forgot";
}
NSString *greeting = [[NSString alloc]
initWithFormat:#"Hello, my name is %#! Really!", nameString];
label.text = greeting;
[greeting release];
}
With this I have been able to place the text from text.input into my label (as stated in label.text = greeting;)
I have another view where I'd like to have someone review this information (view a label too). I need to have access to name or Textinput.text in that other view.
How can I accomplish this?
If you don't need to communicate changes between the two view controllers, you may want to pass it in using a custom init method. This may be best for a confirmation screen, where the prompt would make no sense without this string.
- (id)initWithFrame:(CGRect)aRect username:(NSString*)aName {
if((self = [super initWithFrame:aRect])) {
_myName = [aName retain];
}
return self
}
Another option is to implement a method on the first view controller and call it from the second.
- (NSString*)enteredUsername {
return _myName;
}

On iphone 3.0 how to disable Cut, Copy and Paste option

Does any one know, how can i disable cut, copy and paste option on iPhone 3.0?
Thanks for your help and time.
I, too, couldn't find much documentation on using canPerformAction:withSender: for this purpose. So, I settled for clearing the pasteboard when exiting the application. In my AppDelegate.m:
- (void)applicationWillTerminate:(UIApplication *)application {
NSLog(#"application terminating");
// Clear pasteboard to prevent pasting into other applications:
UIPasteboard *pasteBoard = [UIPasteboard generalPasteboard];
pasteBoard.items = nil;
}
This worked well for my user-annotated reference application. I don't mind users copying and pasting within my application, but I'd rather they not republish my original content.
At some point I'd like more fine-grained control, perhaps with canPerformAction:withSender:, so that I can allow users to copy/paste the content they do create themselves.
Override this method in the controller class.
// Hide cut/copy/paste menu
-(BOOL)canPerformAction:(SEL)action withSender:(id)sender {
if ( [UIMenuController sharedMenuController] )
{
[UIMenuController sharedMenuController].menuVisible = NO;
}
return NO;
}
Any responder (UIView or UIWindow subclass) can override the canPerformAction:withSender: method, so you could just return NO for all the actions you don't want to permit.
See the UIResponder documentation...