Edit and Delete Existing EKEvent? - iphone

I am using the Kal calendar in my app, (hopefully that doesn't change too much) but I get an EKEvent object from that depending on the user selection on the calendar.
Anyway, how can I edit and delete an event which already exists? Namely the EKEvent that I receive?
I need to do this all programatically, I am not using any of Apple's pre-made EKEventViewController's.
I can successfully create and save new events, but Im unsure of how to edit or delete existing ones, any help would be appreciated, thanks.

A complete answer would almost require a demo project.
Other approach would be simply giving you a link to Event Kit Programming Guide.
Since you did not provide any code (what you have tried already) i hope this extract from a working app will push you to the right track.
Note that i sublassed EKEventViewController due to app's specifics - this is not neccessary. I had to sublass it simply because original EKEventViewController
didn't spawn with black navigationBar (this was reported as a bug also, don't now if it's
fixed already).
You know how to add an event to calendar, so there's no need to write about getting a reference to EKEventStore and EKCalendar.
You're also not asking about how to retreive an event from calendar so let's assume you already have some kind of mechanism to select (receive) the event and you want to edit it. Let's say it is:
EKEvent *selectedEvent = (EKEvent *)[events objectAtIndex: selectedIndex];
I create this viewController as a property of appDelegate - you probably have better solution. appDelegate also holds eventStore and defaultCalendar reference - your approach could differ.
appDelegate.detailViewController = [[MPEventViewController alloc] initWithNibName:nil bundle:nil];
appDelegate.detailViewController.event = selectedEvent;
appDelegate.detailViewController.eventStore = appDelegate.eventStore;
appDelegate.detailViewController.defaultCalendar = appDelegate.defaultCalendar;
appDelegate.detailViewController.allowsEditing = NO;
[appDelegate.navigationController pushViewController:appDelegate.detailViewController animated:YES];
Sublcassing (again, this is not neccessary but it might come useful) goes like this:
MPEventEditViewController.h
#import <Foundation/Foundation.h>
#import <EventKitUI/EventKitUI.h>
#interface MPEventViewController : EKEventViewController <EKEventEditViewDelegate>
#property (nonatomic, strong) EKEventStore *eventStore;
#property (nonatomic, strong) EKCalendar *defaultCalendar;
- (void)editEvent:(id)sender;
#end
MPEventEditViewController.m
#import "MPEventViewController.h"
#import "----------AppDelegate.h"
#implementation MPEventViewController
#synthesize eventStore;
#synthesize defaultCalendar;
- (void)viewDidLoad {
[super viewDidLoad];
[self setTitle: [self.event title]];
self.allowsEditing = NO;
self.navigationController.navigationBar.barStyle = UIBarStyleBlack;
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:
UIBarButtonSystemItemEdit target:self action:#selector(editEvent:)];
//this has nothing to do with the answer :)
//[[self.navigationController navigationBar] setTintColor: [UIColor colorWithHexString: NAVBAR_TINT_COLOR]];
}
- (void)editEvent:(id)sender {
EKEventEditViewController *addController = [[EKEventEditViewController alloc] initWithNibName:nil bundle:nil];
//this has nothing to do with the answer :)
//[addController.navigationBar setTintColor: [UIColor colorWithHexString: NAVBAR_TINT_COLOR]];
addController.eventStore = self.eventStore;
addController.event = self.event;
addController.navigationBar.barStyle = UIBarStyleBlack;
addController.editViewDelegate = self;
[self presentModalViewController:addController animated:YES];
}
- (void)eventEditViewController:(EKEventEditViewController *)controller
didCompleteWithAction:(EKEventEditViewAction)action {
NSError *error = nil;
EKEvent *thisEvent = controller.event;
switch (action) {
case EKEventEditViewActionCanceled:
break;
case EKEventEditViewActionSaved:
[controller.eventStore saveEvent:controller.event span: EKSpanFutureEvents error:&error];
break;
case EKEventEditViewActionDeleted:
[controller.eventStore removeEvent:thisEvent span: EKSpanFutureEvents error:&error];
break;
default:
break;
}
//here would be the place to reload data if you hold it in some kind of UITableView
[controller dismissModalViewControllerAnimated:YES];
}
- (EKCalendar *)eventEditViewControllerDefaultCalendarForNewEvents:(EKEventEditViewController *)controller {
EKCalendar *calendarForEdit = self.defaultCalendar;
return calendarForEdit;
}
- (void)dealloc {
eventStore = nil;
defaultCalendar = nil;
}
#end
Only after writing all this i remembered there is a great sample code SimpleEKDemo. In fact some of this posted code is probably originates from there.
EDIT:
After the question was edited the above answer became off-topic.
In that case you should take a look at EKCalendarItem and EKevent.
You can change allmost all properties programatically (most of them are inherited from EKCalendarItem).
Maybe you were distracted for example becaues hasNotes is readonly. That is because hasNotes is kind of a function and not a real property. Properties like notes, atendees, startDate, endDate etc. are perfectly editable.
For saving modified event you can still use:
NSError error = nil;
[self.eventStore saveEvent:event span: EKSpanFutureEvents error:&error];
And for deleting it:
NSError error = nil;
[self.eventStore removeEvent:event span: EKSpanFutureEvents error:&error];
EDIT2: for deleting all events try this:
//assuming self.eventStore is already properly set in code
//identifierArray would be your NSMutableArray holding event identifiers
//change the name according to your code
NSError error = nil;
for (NSString *eventIdentifier in removeAllObjects) {
EKEvent *event = [self.eventStore eventWithIdentifier:eventIdentifier];
[self.eventStore removeEvent:event span:EKSpanFutureEvents error:&error];
}
//now you can also clear identifiers
[removeAllObjects removeAllObjects];
Note: there's no guarantee you'll be able to delete all events - only events from
default calendar which is set by usert in settings app.

Related

issue in reloading tapku calendar

My calendar is working perfectly I show events from calendar in my tapku calendar and I can also edit them so for editing I am using EKEventEditViewController so when editing is completed then
- (void)eventEditViewController:(EKEventEditViewController *)controller
didCompleteWithAction:(EKEventEditViewAction)action
get called and this is how I try to update My calendar here
TKCalendarMonthViewController *tk = [[TKCalendarMonthViewController alloc]init];
[tk loadView];
loadview method is as below and now I will explain whats wrong in comment
this method get called when My calendar is loading. So for loading it again I am calling this method again
- (void) loadView{
[super loadView];
_monthView = [[TKCalendarMonthView alloc] initWithSundayAsFirst:_sundayFirst];
_monthView.delegate = self;
_monthView.dataSource = self;
NSLog(#"::%#",_monthView.dataSource);
NSLog(#"::%#",_monthView.delegate);
[self.view addSubview:_monthView];
[_monthView reload]; // reload method get called
}
- (void) reload{
NSArray *dates = [TKCalendarMonthTiles rangeOfDatesInMonthGrid:[currentTile monthDate] startOnSunday:sunday];
NSLog(#"%#",dates);
NSLog(#"%#",self.dataSource);
// below calendarMonthView:self method get called when my cal is getting called first time by it self but when i try by creating object of class like i showed in that delegate method then below method is not getting called and this is why my cal is not getting updated
// SO THE PROBLEM IS THIS METHOD IS NOT GETTING CALLED
NSArray *ar = [self.dataSource calendarMonthView:self marksFromDate:[dates objectAtIndex:0] toDate:[dates lastObject]];
TKCalendarMonthTiles *refresh = [[TKCalendarMonthTiles alloc] initWithMonth:[currentTile monthDate] marks:ar startDayOnSunday:sunday];
[refresh setTarget:self action:#selector(tile:)];
[self.tileBox addSubview:refresh];
[currentTile removeFromSuperview];
currentTile = refresh;
}
All NSLog's are prints with values non of them is null.
#property (strong,nonatomic) TKCalendarMonthView *monthView;
[self.monthView reload];
note: do not #synthesize property

Use of MBProgressHUD Globally + make it singleton

In my Project, each of the user interaction events make a network call (Which is TCP, not HTTP). I need Activity Indicator to be global to show from a random UIViewController and hide from NetworkActivityManager Class (a custom class to handle network activities, Which is not a subclass of UIViewController or UIView).
After searching the web I found out that MBProgressHUD is used for the same purpose, but I wasn't able to find out an example on how would I use it globally. (By saying global I mean a singleton object of MBProgressHUD and class methods to SHOW and HIDE it.)
Following is what I have tried yet, but, failed:
In AppDelegate.h:
#property (nonatomic, retain) MBProgressHUD *hud;
In AppDelegate.m:
#synthesize hud;
In some random UIViewController object:
appDelegate.hud = [MBProgressHUD showHUDAddedTo:appDelegate.navigationController.topViewController.view animated:YES];
appDelegate.hud.labelText = #"This will take some time.";
And while hiding it, from NetworkActivityManager Class:
[MBProgressHUD hideHUDForView:appDelegate.navigationController.topViewController.view animated:YES];
This makes the project to crash after some time (due to memory issues.)
I am using ARC in my project and also, I am using the ARC version of MBProgressHUD.
Am I missing something?
Important Question:
Can I make MBProgressHUD work like UIAlertView? (Saying that I mean implementation of MBProgressHUD independent of UIView -- sa it uses showHUDAddedTo: to present itself) ???
Please Note: In the above code of hiding MBProgressHUD, View may be changed from what it was when showing MBProgressHUD.
Any Help greatly appreciated.
You could add this to a class of your liking:
+ (MBProgressHUD *)showGlobalProgressHUDWithTitle:(NSString *)title {
UIWindow *window = [[[UIApplication sharedApplication] windows] lastObject];
MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:window animated:YES];
hud.labelText = title;
return hud;
}
+ (void)dismissGlobalHUD {
UIWindow *window = [[[UIApplication sharedApplication] windows] lastObject];
[MBProgressHUD hideHUDForView:window animated:YES];
}
This can be than called on any class. You don't need to keep a strong reference to the HUD when using those class convenience methods.
Depending on your specific situation you'll probably also want to handle cases where a new hud is requested before the other one is hidden. You could eater hide the previous hud when a new comes in or come up with some sort of queueing, etc.
Hiding the previous HUD instance before showing a new one is pretty straightforward.
+ (MBProgressHUD *)showGlobalProgressHUDWithTitle:(NSString *)title {
UIWindow *window = [[[UIApplication sharedApplication] windows] lastObject];
[MBProgressHUD hideAllHUDsForView:window animated:YES];
MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:window animated:YES];
hud.labelText = title;
return hud;
}
NOTE...
as with many iOS issues, this is now drastically, totally out of date.
These days you certainly just use a trivial
Container view
for any issue like this.
Full container view tutorial for beginners .. tutorial!
MBProgressHUD was a miraculous solution back in the day, because there was a "drastic hole" in Apple's pipeline.
But (as with many wonderful things from the past), this is only history now. Don't do anything like this today.
Just FWIW, 2014, here's a very simple setup we use. Per David Lawson...
UIWindow *window = [[UIApplication sharedApplication] delegate].window
as Matej says, just use AppDelegate...
#define APP ((AppDelegate *)[[UIApplication sharedApplication] delegate])
AppDelegate.h
// our convenient huddie system (messages with a hud, spinner)
#property (nonatomic, strong) MBProgressHUD *hud;
-(void)huddie;
AppDelegate.m
-(void)huddie
{
// centralised location for MBProgressHUD
[self.hud hide:YES];
UIWindow *windowForHud = [[UIApplication sharedApplication] delegate].window;
self.hud = [MBProgressHUD showHUDAddedTo:windowForHud animated:YES];
self.hud.dimBackground = YES;
self.hud.minShowTime = 0.1;
self.hud.labelText = #"";
self.hud.detailsLabelText = #"";
}
Set the titles in your code where you are using it - because you very often change them during a run. ("Step 1" ... "Step 2" etc)
-(void)loadBlahFromCloud
{
[APP huddie];
APP.hud.labelText = #"Connecting to Parse...";
APP.hud.detailsLabelText = #"step 1/2";
[blah refreshFromCloudThen:
^{
[... example];
}];
}
-(void)example
{
APP.hud.labelText = #"Connecting to the bank...";
APP.hud.detailsLabelText = #"step 2/2";
[blah sendDetailsThen:
^{
[APP.hud hide:YES];
[... showNewDisplay];
}];
}
Change huddle to take the texts as an argument if you wish
You always want self.hud.minShowTime = 0.1; to avoid flicker
Almost always self.hud.dimBackground = YES; which also blocks UI
Conceptually of course you usually have to "slightly wait" to begin work / end work when you bring up such a process, as with any similar programming with the UI.
So in practice code will usually look like this...
-(void)loadActionSheets
{
[APP huddie];
APP.hud.labelText = #"Loading json from net...";
dispatch_after_secs_on_main(0.1 ,
^{
[STUBS refreshNowFromCloudThen:
^{
[APP.hud hide:YES];
dispatch_after_secs_on_main(0.1 , ^{ [self buildActionsheet]; });
}];
}
);
}
Handy macro ..
#define dispatch_after_secs_on_main( SS, BB ) \
dispatch_after( \
dispatch_time(DISPATCH_TIME_NOW, SS*NSEC_PER_SEC), \
dispatch_get_main_queue(), \
BB \
)
This is all history now :) https://stackoverflow.com/a/23403979/294884
This answer is what I've been using for 5-6 Apps now because it works perfectly inside blocks too. However I found a problem with it. I can make it shown, but can't make it disappear if a UIAlertView is also present. If you look at the implementation you can see why. Simply change it to this:
static UIWindow *window;
+ (MBProgressHUD *)showGlobalProgressHUDWithTitle:(NSString *)title {
window = [[[UIApplication sharedApplication] windows] lastObject];
MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:window animated:YES];
hud.labelText = title;
return hud;
}
+ (void)dismissGlobalHUD {
[MBProgressHUD hideHUDForView:window animated:YES];
}
This will make sure you're removing the HUD from the same windows as it was shown on.
I found #Matej Bukovinski 's answer very helpful, since I just started using Swift and my purpose using his methods was to set a global font for the MBProgressHUD, I have converted the code to swift and am willing to share the code below:
class func showGlobalProgressHUDWithTitle(title: String) -> MBProgressHUD{
let window:UIWindow = UIApplication.sharedApplication().windows.last as! UIWindow
let hud = MBProgressHUD.showHUDAddedTo(window, animated: true)
hud.labelText = title
hud.labelFont = UIFont(name: FONT_NAME, size: 15.0)
return hud
}
class func dismissGlobalHUD() -> Void{
let window:UIWindow = UIApplication.sharedApplication().windows.last as! UIWindow
MBProgressHUD.hideAllHUDsForView(window, animated: true)
}
The above code is put into a global file where I keep all my global helpers and constants.
I've used it as below..Hope it helps you..
in appDelegate.m
-(void)showIndicator:(NSString *)withTitleString currentView:(UIView *)currentView
{
if (!isIndicatorStarted) {
// The hud will dispable all input on the view
self.progressHUD = [[[MBProgressHUD alloc] initWithView:currentView] autorelease];
// Add HUD to screen
[currentView addSubview:self.progressHUD];
self.progressHUD.labelText = withTitleString;
[window setUserInteractionEnabled:FALSE];
[self.progressHUD show:YES];
isIndicatorStarted = TRUE;
}
}
-(void)hideIndicator
{
[self.progressHUD show:NO];
[self.progressHUD removeFromSuperview];
self.progressHUD = nil;
[window setUserInteractionEnabled:TRUE];
isIndicatorStarted = FALSE;
}
From Random Views:-
[appDel showIndicator:#"Loading.." currentView:presentView.view];
Note: Considering the views this Question is getting I decided to post the the way I did choose as a solution. This is NOT an answer to my question. (Hence, the accepted answer remains accepted)
At that time I ended up using SVProgressHUD as it was very simple to integrate and use.
All you need to do is just drag the SVProgressHUD/SVProgressHUD folder into your project. (You may choose to go for cocoapods OR carthage, as well)
In Objective-C:
[SVProgressHUD show]; // Show
[SVProgressHUD dismiss]; // Dismiss
In Swift:
SVProgressHUD.show() // Show
SVProgressHUD.dismiss() // Dismiss
Additionally, Show and hide HUD needs to be executed on main thread. (Specifically you would need this to hide the HUD in some closure in background)
e.g.:
dispatch_async(dispatch_get_main_queue(), ^{
[SVProgressHUD dismiss]; // OR SHOW, whatever the need is.
});
There are additional methods for displaying custom messages with HUD, showing success/failure for short duration and auto dismiss.
MBProgressHUD still remains a good choice for developers. It's just that I found SVProgressHUD to suit my needs.
I was using the code from #Michael Shang and having all kinds of inconsistent behavior with showing HUDs. Turns out using the last window is unreliable as the iOS keyboard may just hide it. So in the majority of cases you should get the window using the AppDelegate as mentioned by #David Lawson.
Here's how in Swift:
let window = UIApplication.sharedApplication().delegate!.window!!
let hud = MBProgressHUD.showHUDAddedTo(window, animated: true)
However, with the above your HUD will show up behind the iOS keyboard (if they overlap). If you need your HUD to overlay the keyboard use the last window method.
In my case, what was happening is I would show the HUD then call resignFirstResponder() immediately hiding the window the HUD was added to. So this is something to be aware of, the only window guaranteed to stick around is the first one.
I ended up creating a method that could optionally add the HUD above the keyboard if needed:
func createHUD(size: CGSize, overKeyboard: Bool = false) -> MBProgressHUD {
let window = overKeyboard ? UIApplication.sharedApplication().windows.last!
: UIApplication.sharedApplication().delegate!.window!!
let hud = MBProgressHUD.showHUDAddedTo(window, animated: true)
hud.minSize = size
hud.bezelView.style = .SolidColor
hud.bezelView.color = UIColor(white: 0, alpha: 0.8)
return hud
}
To show the one MBProgressHUD at one time, you can check weather HUD is already added in same view or not. If not, then add the HUD otherwise do not add new HUD.
-(void)showLoader{
dispatch_async(dispatch_get_main_queue(), ^{
BOOL isHudAlreadyAdded = false;
UIWindow *window = [[[UIApplication sharedApplication] windows] lastObject];
NSEnumerator *subviewsEnum = [window.subviews reverseObjectEnumerator];
for (UIView *subview in subviewsEnum) {
if ([subview isKindOfClass:[MBProgressHUD class]]) {
isHudAlreadyAdded = true;
}
}
if(isHudAlreadyAdded == false){
[MBProgressHUD showHUDAddedTo:window animated:YES];
}
});
}
-(void)hideLoader{
dispatch_async(dispatch_get_main_queue(), ^{
UIWindow *window = [[[UIApplication sharedApplication] windows] lastObject];
[MBProgressHUD hideHUDForView:window animated:YES];
});
}
Add these two methods to show or hide loader in your singleton class
- (void)startLoaderWithText:(NSString *)title View:(UIView *)view{
progressHud = [MBProgressHUD showHUDAddedTo:view animated:YES];
progressHud.labelText = title;
progressHud.activityIndicatorColor = [UIColor grayColor];
progressHud.color = [UIColor clearColor];
[progressHud show:YES];
}
- (void)stopLoader{
[progressHud hide:YES];
}

GADInterstitial custom ads not working

i have a problem in getting the GADInterstitial custom ads i have tried this code
if(nil != m_pBannerView)
{
m_pBannerView.delegate = nil;
[m_pBannerView release];
m_pBannerView = nil;
}
m_pBannerView = [[GADBannerView alloc] initWithAdSize:kGADAdSizeBanner];
m_pBannerView.delegate = self;
m_pBannerView.rootViewController = self;
m_pBannerView.adUnitID = #"AdMob Publisher ID";
m_pBannerView.rootViewController = self;
[self.view addSubview:m_pBannerView];
GADRequest *request = [GADRequest request];
request.testing = YES;
[m_pBannerView loadRequest:request];
if(nil != m_pInterstitial)
{
[m_pInterstitial release];
m_pInterstitial = nil;
}
m_pInterstitial = [[GADInterstitial alloc] init];
m_pInterstitial.delegate = self;
m_pInterstitial.adUnitID = #"INTERSTITIAL_AD_UNIT_ID";
GADRequest *interstialRequest = [GADRequest request];
interstialRequest.testing = YES;
[m_pInterstitial loadRequest: interstialRequest];
}
And in GADInterstitial Delegates i am calling [ad presentFromRootViewController:self];
but still i am not able to get the custom ads please help me.
You have to use your own unique id for adUnitID property
GADInterstitial is an interesting way to show ads in your Apps, and kind of a tricky one too. Following this example, lets pursue the following steps:
First we need to set up the environment for them to show at all.
Download the GoogleAdMobAdsSdkiOS, preferably the latest. Add
the SDK to your project, but do remember to delete the example
Projects in the AddOns folder in the SDK.
Next, add the following frameworks in your Project>>Build
Phases>>Link Binary With Libraries:
AdSupport.framework (select Optional if catering for < iOS7)
StoreKit.framework
CoreData.framework
CoreAudio.framework
AVFoundation.framework
MessageUI.framework
AudioTool.framework
libGoogleAdMobAds.a (placed in the SDK folder)
The basics are complete. Now we need to select the ViewController we wish to see our Ads in. So here's some code, here we import the header for GADInterstitialDelegate and extend it with our MainViewController.h:
#import "GADInterstitialDelegate.h"
#define kSampleAdUnitID #"/6253334/dfp_example_ad/interstitial"
#class GADInterstitial;
#class GADRequest;
#interface MainViewController : UIViewController<GADInterstitialDelegate>
{
BOOL isLoaded_;
NSTimer *quickie_;
}
#property(nonatomic, strong) GADInterstitial *interstitial;
//Make sure the delegate is handled properly.
Now we need to move to the implementation i.e. in MainViewController.m:
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
// FOLLOW THE CODE BELOW FOR ADMOD INTERESTIAL IMPLEMENTATION
[self initializeAds];
quickie_ = [[NSTimer alloc] init];
quickie_ = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(showAdd) userInfo:nil repeats:YES];
}
-(void)showAdd
{
if(isLoaded_ && [quickie_ isValid])
{
[quickie_ invalidate];
quickie_ = nil;
[self.interstitial presentFromRootViewController:self];
}
}
- (GADRequest *)request
{
GADRequest *request = [GADRequest request];
return request;
}
-(void)initializeAds
{
// Create a new GADInterstitial each time. A GADInterstitial will only show one request in its
// lifetime. The property will release the old one and set the new one.
self.interstitial = [[GADInterstitial alloc] init];
self.interstitial.delegate = self;
// Note: kSampleAdUnitId is a staticApId for example purposes. For personal Ads update kSampleAdUnitId with your interstitial ad unit id.
self.interstitial.adUnitID = kSampleAdUnitID;
[self.interstitial loadRequest:[self request]];
}
- (void)viewWillLayoutSubviews
{
[super viewWillLayoutSubviews];
self.loadingSpinner.center = CGPointMake(CGRectGetWidth(self.view.bounds) / 2, self.loadingSpinner.center.y);
}
- (void)interstitialDidReceiveAd:(GADInterstitial *)interstitial
{
isLoaded_ = YES;
}
- (void)interstitial:(GADInterstitial *)interstitial didFailToReceiveAdWithError:(GADRequestError *)error
{
isLoaded_ = NO;
}
//----ADS IMPLEMENTATION TILL HERE--->//
The timer "quickie_" here constantly checks if the Ad has successfully loaded and when it does, it shoots the Ad on the ViewController if the user still is on it. The static kSampleAdUnitID is a sampleId that always works. Thats it. Run your code and find your Interstitial Ad on your ViewController of choice.
Hope I helped. Cheers! :)

Need Help with applicationDidBecomeActive

I have been trying for days to get this code to work, but I have no idea what I am doing wrong. Everytime the app wakes up from sleep, or the user closes the app and opens it again (without closing the app from multitasking), I want a label value to change.
In my applicationDidBecomeActive, I am running a counter, which I want to display on whatever viewcontroller is open at that moment.
Code:
- (void)applicationDidBecomeActive:(UIApplication *)application {
counter = counter + 1;
W1G1 *view1 = [[[W1G1 alloc] initWithNibName:#"W1G1" bundle:nil] retain];
[view1 setlabel];
}
In my viewcontroller W1G1, I have the following code:
Code:
- (void) setlabel {
NSString *string = [NSString stringWithFormat:#"%d", counter];
vocabword.text = string;
}
I have imported W1G1 in my appdelegate, but the code does not run :( Please help!
Thanks
In the AppDelegate.m file, where you have
- (void)applicationDidBecomeActive:(UIApplication *)application {
counter = counter + 1;
W1G1 *view1 = [[[W1G1 alloc] initWithNibName:#"W1G1" bundle:nil] retain];
[view1 setlabel];
}
the variable counter being incremented is confined to the AppDelegate. In other words, your view controller doesn't know that it has been incremented.
I would suggest that you use NSUserDefaults to store the value of counter so that you can easily pass it between these view controllers. Either that, or you could allow for an input into the method setLabel, e.g.
- (void) setlabel:(int)counter {
NSString *string = [NSString stringWithFormat:#"%d", counter];
vocabword.text = string;
}
and then in the AppDelegate you'll want to do:
- (void)applicationDidBecomeActive:(UIApplication *)application {
counter = counter + 1;
W1G1 *view1 = [[[W1G1 alloc] initWithNibName:#"W1G1" bundle:nil] retain];
[view1 setlabel:counter]; // <-- now you're using counter
[self.window addSubview:view1];
}
1) When you say 'the code does not run' do you mean that? That is, if you put NSLogs in applicationDidBecomeActive: and in setLabel does it show the code is run?
2) I would suspect the code is running. But your code won't "show the counter on whatever view controller is open at that moment". Your code creates a new view (view1), but that view won't be displayed. It is not added as a subview to anything. Your code will also leak. You create a W1G1 object, but it is never released and you throw away any reference you have to it.
To achieve what you want, you could add a subview to the application's window. Depending how your app delegate is set up, something like the following should do the trick:
counter++;
W1G1 *viewController1 = [[W1G1 alloc] initWithNibName:#"W1G1" bundle:nil];
[viewController1 setlabel: counter];
[[self window] addSubview: [viewController1 view]]
// you'll want to save a reference to the viewController somehow so you can release it at a later date
Then in W1G1
- (void) setlabel: (int) counter;
{
NSString *string = [NSString stringWithFormat:#"%d", counter];
vocabword.text = string;
}
There are, of course, lots of other approaches you could take towards this problem. And you'll need some strategy for removing the W1G1 view that you are adding at some stage, otherwise you'll just get more and more views added.
Update: You ask (in comments) how to keep track of your viewController throughout lifetime of the app... One approach is to keep track of it in your appDelegate. In the header have something like:
#class W1G1;
#interface MyAppDelegate : : NSObject <UIApplicationDelegate>
{
// other decelerations
int counter;
W1G1 * _myW1G1
}
#property (nonatomic, retain) W1G1* theW1G1
In the .m file include
#synthesize theW1G1 = _myW1G1;
Probably in application:didFinishLaunchingWithOptions: create the viewController, set the property to refer to it, and add its view to the view hierarchy.
W1G1* theViewController = [[W1G! alloc] initWithNibName: #"W1G1" bundle: nil];
[[self window] addSubview: [theViewController view]];
[self setTheW1G1: theViewController];
[theViewController release];
Then when you want to access the viewController again from with the app delegate use [self theW1G1], e.g.
[[self W1G1] setlabel: counter];

CoreData weird behavior when data are loaded on background thread

I have very strange problem, when I don't understand what's going on at all, so I'm looking for explanation of it. Situation is as following:
I have a view controller with scrollview with three subviews in it. Those three subviews have method
-(void)loadContent
which loads content from database using CoreData in the background thread, creates subviews which represent loaded items and add them as own subviews calling [self addSubview: itemView]; That method is invoked as
[self performSelectorInBackground: #selector(loadContent) withObject: nil];
To load data from DB I'm using a singleton service class. Everything worked fine, but when those three views are loading their portions of data, it sometimes crashes the app.
I guessed it's because it shares one NSManagedObjectContext instance for all read operations, so I rewrote the class so it shares only NSManagedObjectModel and NSPersistentStoreCoordinator instances and creates it's own NSManagedObjectContext instance.
Suddenly, very strange thing happened. Data are loaded ok, subviews are created and added to the view hierarchy, but it get never displayed on the screen. When I switch back to the old singleton service class (sharing one managedObjectContext), it works again like a charm! (but with risk of crashing the app, though).
I absolutely don't get the point how loading data from DB is related to displaying items on the screen. More on that - when subviews are created and added to the view hierarchy, why the hell it don't get displayed?
The source looks like this:
- (void) loadContent {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSArray *results = [(WLDataService *)[WLDataService service] loadItemsForGDView];
NSUInteger channelPosition = 0;
CGFloat position = 0.0;
CGFloat minuteWidth = ((self.superview.frame.size.width / 2.0) / 60.0);
for(Item *it in results) {
/// On following lines size and position of the view is computed according to item setup - skipping here...
/// Create item; it's simple subclass of UIView class
WLGDItemView *item = [[WLGDItemView alloc] init];
/// Variables used here are declared above when size and position is computed
item.frame = CGRectMake(itemX, itemY, itemWidth, itemHeight);
[self performSelectorOnMainThread: #selector(addSubview:) withObject: item waitUntilDone: NO];
/// This is just helper macro to release things
WL_RELEASE_SAFELY(item);
}
[pool drain];
}
The basic service class (non-singleton one) implementation is as follows (just interesting parts):
#import "WLLocalService.h"
static NSPersistentStoreCoordinator *sharedPSC = nil;
static NSManagedObjectModel *sharedMOM = nil;
#implementation WLLocalService
#synthesize managedObjectContext;
/// This is here for backward compatibility reasons
+ (WLLocalService *) service {
return [[[self alloc] init] autorelease];
}
#pragma mark -
#pragma mark Core Data stack
- (NSManagedObjectContext *) managedObjectContext {
if (managedObjectContext == nil) {
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil) {
managedObjectContext = [[NSManagedObjectContext alloc] init];
[managedObjectContext setPersistentStoreCoordinator: coordinator];
}
[managedObjectContext setUndoManager: nil];
[managedObjectContext setMergePolicy: NSMergeByPropertyStoreTrumpMergePolicy];
}
return managedObjectContext;
}
- (NSManagedObjectModel *) managedObjectModel {
if(sharedMOM == nil) {
sharedMOM = [[NSManagedObjectModel mergedModelFromBundles: nil] retain];
}
return sharedMOM;
}
- (NSPersistentStoreCoordinator *) persistentStoreCoordinator {
if(sharedPSC == nil) {
NSURL *storeUrl = [self dataStorePath];
NSError *error = nil;
sharedPSC = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel: [self managedObjectModel]];
if (![sharedPSC addPersistentStoreWithType: NSSQLiteStoreType configuration: nil URL: storeUrl options: nil error: &error]) {
WLLOG(#"%#: %#", error, [error userInfo]);
}
}
return sharedPSC;
}
#pragma mark -
#pragma mark Path to data store file
- (NSURL *) dataStorePath {
return [NSURL fileURLWithPath: [WL_DOCUMENTS_DIR() stringByAppendingPathComponent: #"/DB.sqlite"]];
}
- (void)dealloc {
WL_RELEASE_SAFELY(managedObjectModel);
[super dealloc];
}
#end
I'd really love to know what's going on here and why it behaves so strange (and - of course - why it does not work, in particular). Can anybody explain that?
thanks to all
Have you read Multi Threading with Core-Data twice?
First, do not load or construct UI elements on a background thread. The UI (whether on the desktop or on the iPhone) is single threaded and manipulating it on multiple threads is a very bad idea.
Second, data that you load into one context will not be immediately visible in another context. This is what is causing part of your problem.
The solution is to move all your UI code to the main thread and warm up the Core Data cache on a background thread. This means to load the data on a background thread (into a separate cache) to load it into the NSPersistentStoreCoordinator cache. Once that is complete your main thread can access that data very quickly because it is now in memory.
You realize that [WLDataService service] does not actually return a singleton? It creates a new instance every time. So you are effectively working with multiple instances of the Core Data components.
What about:
static WLDataService* gSharedService = NULL;
#implementation WLDataService
+ (id) service
{
#synchronized (self) {
if (gSharedService == NULL) {
gSharedService = [[self alloc] init];
}
}
return gSharedService;
}
#end
That will create the same instance every time. You will also want to make your managedObjectContext, managedObjectModel and persistentStoreCoordinator methods thread safe by using a #synchronized block. Otherwise there is a change that multiple threads will initialize those at the same time, leading to unexpected behaviour.