I'm trying to set the alpha of a label, inside a block statement and therefore I'm calling self. As I understand it, I can't call self directly from within a block statement, so I have to make a reference of self first.
This is the code I have, but it's not working:
self.accountStore = [[ACAccountStore alloc] init];
__weak UILabel *weakSelf = self.errorLabel;
[self.accountStore requestAccessToAccountsWithType:twitterType options:NULL completion:^(BOOL granted, NSError *error) {
if (!granted) {
[weakSelf setAlpha:0.0f];
}
}];
Any ideas of what might be the problem?
UPDATE 1
I 've also tried to only reference self, but with no luck:
self.accountStore = [[ACAccountStore alloc] init];
__weak FrontPageViewController *weakSelf = self;
[self.accountStore requestAccessToAccountsWithType:twitterType options:NULL completion:^(BOOL granted, NSError *error) {
if (!granted) {
[weakSelf.errorLabel setAlpha:0.0f];
}
}];
UPDATE 2
Just checked if error label is nil and it doesn't seem to be:
if (self.errorLabel != nil) {
NSLog(#"Errorlabel is not nil"); //Errorlabel is not nil
}
CAUSE OF ERROR
The error was that I had this code right after I wanted to fade out the label:
[UIView animateWithDuration:0.2f animations:^{
//self.errorLabel.alpha = 0.0f;
} completion:^(BOOL success){
}];
I don't fully understand why this should cause trouble?
You need to ensure all your UI calls are made from the main thread. This includes any animateWith... calls. The quickest way is to simply wrap them in a dispatch block, like so:
dispatch_async(dispatch_get_main_queue(), ^{
[UIView animateWithDuration:0.2f
animations:^{
self.errorLabel.alpha = 0.0f;
}
completion:nil];
});
If you are unsure if your code is running on the main thread you can debug with the following statement.
NSLog(#"Is main thread = %#",(dispatch_get_main_queue() == dispatch_get_current_queue())?#"YES":#"NO");
Always lookout for completion handlers on asynchronous network APIs. Make sure their documentation says the completion handler will be called on the main thread. If it doesn't, play it safe and transfer any UI related work to the main thread.
__block __weak UILabel *weakSelf = self.errorLabel;
Related
I have a loop function and in it called [NSThread sleepForTimeInterval:2.0];. it mean after 2s, loop function is called. I want when pass new view, this loop function is stop and when back, it is called again.
I use this code to call loop function:
-(void) viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
loop = YES;
delete=NO;
temp = [FileCompletedArray mutableCopy];
NSOperationQueue *queue = [NSOperationQueue new];
operations = [[NSInvocationOperation alloc] initWithTarget:self selector:#selector(updateArray) object:nil];
[queue addOperation:operations];
[operations release];
}
And loop function:
-(void)updateArray{
while (loop)
{
NSLog(#"start loop");
if(loop){
[NSThread sleepForTimeInterval:2.0];
NSLog(#"start send request");
NSURL *url1 = [NSURL URLWithString:#"http://server.com"];
NSMutableURLRequest *afRequest = [httpClient requestWithMethod:#"POST" path:nil parameters:params1] ;
operation= [[AFHTTPRequestOperation alloc] initWithRequest:afRequest];
NSLog(#" request sent");
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"Server response1");
}
failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"error: %#", error);
}
];
[httpClient enqueueHTTPRequestOperation:operation];
}
else
return;
}
}
And viewdisappear()
-(void) viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
loop = NO;
delete=NO;
[operations cancel] ;
}
My problem is when pass new view, updateArray still call. It not stop. Do you have suggestion?
You can try it using key value observers. You can implement the changes in following method which will be automatically called as a particular value changes. First you have to set the notification for the ivar that is going to be changed.
[self addObserver:self forKeyPath:#"loop" options:NSKeyValueObservingOptionNew context:nil];
Then you have to implement the changes as per requirement in the following method:
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
There are a couple of issues that leap out at me:
If your intent is to actually cancel the sleepForTimeInterval, I don't believe you can do that. There are other mechanisms (such as timers) that are much better suited for that problem.
As an aside, you are issuing an asynchronous request every two seconds. But you have no assurances that your previous request will have completed in that period of time, though. As a result, you can end up with a backlog of multiple network requests that will continue to run after the view is dismissed.
I would have thought that you'd want to initiate the "wait two seconds" inside the completion block of your asynchronous request, to ensure your requests don't get backlogged behind your "every two seconds" logic. Clearly that won't work with your current while loop unless you make the request synchronous, so you might refactor this code, replacing the while loop with something that performs a single request, and in the completion block, waits two seconds before initiating the next request.
You are checking the state of loop, waiting two seconds, and then issuing your request. So if the view disappeared while it was performing the two second sleep, there's nothing here stopping the request from being issued after you finished sleeping.
At the very least, if you're going to use sleepForTimeInterval, you presumably want to check loop state after you finish sleeping. But, to my first point, it's better to use some cancelable mechanism, such as a timer.
If you're saying that your loop never exits, I'd suggest you check to make sure the appearance methods are getting called like you think they should be.
Personally, I'd be inclined to do this with a timer which can easily be canceled/invalidated:
Define my properties:
#property (nonatomic, strong) NSTimer *timer;
#property (nonatomic, getter = isLooping) BOOL looping;
#property (nonatomic, weak) AFHTTPRequestOperation *operation;
Have my appearance methods set the looping variable and start/stop the scheduled requests as appropriate:
- (void)viewDidAppear:(BOOL)animated
{
self.looping = YES;
[self scheduleRequestIfLooping];
}
- (void)viewWillDisappear:(BOOL)animated
{
self.looping = NO;
[self cancelScheduledRequest];
}
The methods that do the starting and stopping of the scheduled requests would use the NSTimer:
- (void)scheduleRequestIfLooping
{
if ([self isLooping]) {
self.timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:#selector(initiateRequest:) userInfo:nil repeats:NO];
}
}
- (void)cancelScheduledRequest
{
[self.timer invalidate];
self.timer = nil;
// you might want to cancel any `AFHTTPRequestOperation`
// currently in progress, too
[self.operation cancel];
}
Note, whether the cancel method should cancel both the timer and any current request in progress (if any) is up to you.
Finally, put the scheduling of the next request inside the completion block of the current request.
- (void)initiateRequest:(NSTimer *)timer
{
// create AFHTTPRequestOperation
AFHTTPRequestOperation operation = ...
// now schedule next request _after_ this one, by initiating that request in the completion block
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"Server response1");
[self scheduleRequestIfLooping];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"error: %#", error);
// not sure if you want to schedule request if last one failed
}];
self.operation = operation; // save this in the property
}
In the above, I'm using the main thread for the timer. If you really want to do it on a background queue, you can, but (a) it seems unnecessary to me as the network requests already happen on a background thread; and (b) if you do this on a background thread, you might want to make sure you're doing the necessary thread-safe handling of your state variables and the like. It just seemed like an unnecessary complication to me.
i´m getting these warnings for my code below.
Any ideas how to fix that?
Thanks for any help.
Type specifier missing, defaults to 'int'
Incompatible pointer to integer conversion initializing 'int' with an expression of type 'void *';
Unused variable 'mymoviePlayerController'
The important line is the "__block mymoviePlayerController = nil;
- (void) moviePlaybackCompleteLightBox:(NSNotification*) notification {
MPMoviePlayerController *mymoviePlayerController = [notification object];
[[NSNotificationCenter defaultCenter] removeObserver:self
name:MPMoviePlayerPlaybackDidFinishNotification
object:mymoviePlayerController];
// movie fadein transition ====================
self.moviePlayerController.view.alpha = 1;
[UIView animateWithDuration:0.3f delay:0.0 options:UIViewAnimationCurveEaseOut
animations:^{
self.moviePlayerController.view.alpha = 0;
}
completion:^(BOOL finished) {
[mymoviePlayerController stop];
[mymoviePlayerController.view removeFromSuperview];
__block mymoviePlayerController = nil;
}];
}
__block is used when you declare variable, not when you assign value to it. So compiler treats the following line as variable declaration, which is wrong:
__block mymoviePlayerController = nil;
You should use __block attribute when declare variable:
__block MPMoviePlayerController *mymoviePlayerController = [notification object];
P.S. Why do you use __block here anyway? It looks you don't need it in this situation
First, you don't have to set the mymoviePlayerController variable to nil, if you don' use it afterwards. Just don't worry about it, removing the controller's view from its superview is enough.
Second, you can't make a variable writable using the __block qualifier inside of a block. You'll have to modify your code to make the variable writable outside of the block:
__block MPMoviePlayerController *blockMoviePlayerController = mymoviePlayerController;
[UIView animate...animations:...complection:^(BOOL finished) {
blockMoviePlayerController = nil; // or something else
}];
Here is my code:
-(IBAction)saveDownloadedImage
{
NSLog(#"Test"); EXECUTED
indicatorView.hidden = NO; NOT EXECUTED
[indicatorView startAnimating]; NOT EXECUTED
[statusLabel setText:#"WHY?"]; NOT EXECUTED
[currentPicture setImage:[imageView image]]; EXECUTED
ImageFileManager *fileManager = [[ImageFileManager alloc] init]; EXECUTED
[fileManager saveImageToDisk:currentPicture]; EXECUTED
indicatorView.hidden = YES;
[statusLabel setText:#"Image saved successfully."]; EXECUTED
saveButton.enabled = NO; EXECUTED
}
The proccess of saving takes about 5 seconds. So it would be normal to see the indicator in the UI. But nothing happens! Any idea?
Everything is executed. Your problem is that the saveImageToDisk call is synchronous and you are calling it from the UI thread. When you are blocking the UI thread, nothing is ever repainted. The indicator is shown but it cannot be drawn to the screen until the IBAction returns when it will have been hidden again.
You have to call the saving method asynchronously.
Blocking UI thread is never a good idea.
Edit: see the answer for the following question for the correct solution: asynchronous calls to database in ios
Edit2: One of the possible solutions (not tested)
-(IBAction)saveDownloadedImage {
indicatorView.hidden = NO; //Note you can use hidesWhenStopped property for this
[indicatorView startAnimating];
[statusLabel setText:#"BECAUSE..."];
[currentPicture setImage:[imageView image]];
[NSThread detachNewThreadSelector:#selector(save) toTarget:self withObject:nil]
}
- (void)save {
#autoreleasepool {
ImageFileManager *fileManager = [[ImageFileManager alloc] init];
[fileManager saveImageToDisk:currentPicture];
[self performSelectorOnMainThread:#selector(updateUI) withObject:nil waitUntilDone:NO];
}
}
- (void)updateUI {
indicatorView.hidden = YES;
[statusLabel setText:#"Image saved successfully."];
saveButton.enabled = NO;
}
Are you sure that
1) indicatorView and statusLabel are not null, and
2) indicatorView and statusLabel are added as subviews to self.view?
In My Perception your are starting the Activity Indicator main Thread.
Instead Showing the Indicator on main thread
you should call Indicator on seperate Thread as Below.
-(IBAction)saveDownloadedImage
{
NSLog(#"Test"); EXECUTED
commented below code
//indicatorView.hidden = NO; NOT EXECUTED
// [indicatorView startAnimating]; NOT EXECUTED
//instead of main thread create new Thread as Below
[NSThread detachNewThreadSelector:#selector(showloader) toTarget:self withObject:nil];
[statusLabel setText:#"WHY?"]; NOT EXECUTED
[currentPicture setImage:[imageView image]]; EXECUTED
ImageFileManager *fileManager = [[ImageFileManager alloc] init]; EXECUTED
[fileManager saveImageToDisk:currentPicture]; EXECUTED
[statusLabel setText:#"Image saved successfully."]; EXECUTED
saveButton.enabled = NO;
[indicatorView stopAnimating:YES];
indicatorView.hidden = YES;
}
//Custome method For shoing the Indicator.
-(void)showloader{
//call below method here indicatorView object created already.
[indicatorView startAnimating]
}
It'll definitely work
You need to declare your method with a sender like this
-(IBAction)saveDownloadedImage:(id)sender
I have a problem with removing a subview, or more precisely with checking if it is still there after having deleted it.
My app first adds a subview to self.view.
[self.view addSubview:tabsClippedView];
Then it adds another subview to this subview (to which it adds several buttons as subviews, but I guess this is unimportant in this context):
[tabsClippedView addSubview:tabsView];
Finally, I have a method which allows the tabsView to be deleted and then created again. I need to do this so as to update the number of buttons in that tabsView (as the user can delete buttons). The method looks basically like this:
[self.tabsView removeFromSuperview];
After that I call a method called showTabs (which I already called in the very beginning of the app in order to add the subViews). This is where it all becomes problematic and where my app crashes (I get no error in the debug console, so I don't really know what the issue is...):
if ([tabsClippedView isDescendantOfView:self.view]) {
NSLog(#"There is already a tabsClippedView.");
} else {
NSLog(#"There is no tabsClippedView. I'll add one...");
[self initTabsClippedView];
}
This is where the app crashes: when trying to assess if tabsView isDescendantOfView (I don't get any of the following logs):
if ([tabsView isDescendantOfView:tabsClippedView]) {
NSLog(#"There is already a tabsView");
} else {
NSLog(#"There is no tabsView for the buttons. I'll add one including buttons.");
[self initTabs];
}
I'd be grateful for any suggestions where the problem could be.
EDIT:
These are the methods to set up my views:
-(void) initTabsClippedView { // sets up tabsClippedView
NSLog(#"initTabsClippedView method started...");
CGRect tabsClippedFrame = CGRectMake(258,30,70,81*6);
tabsClippedView = [[[UIView alloc] initWithFrame:tabsClippedFrame] autorelease];
tabsClippedView.clipsToBounds = true;
[self.view addSubview:tabsClippedView];
NSLog(#"initTabsClippedView method ended.");
}
-(void) initTabs {
NSLog(#"initTabs started. Adding buttons to tabsClippedView...");
CGRect tabsFrame = CGRectMake(-30,0,50,480);
tabsView = [[[UIView alloc] initWithFrame:tabsFrame] autorelease];
[tabsClippedView addSubview:tabsView];
// sets up buttons in tabsClippedView
And this is where I delete the tabsClippedView (triggered by a button found in tabsClippedView):
-(void)tabDelete:(id)sender
{
UIButton *button = (UIButton *)sender;
[UIView animateWithDuration:0.75
delay:0
options:UIViewAnimationOptionCurveEaseIn
animations:^{
button.transform = CGAffineTransformMakeTranslation(-30, 0);
}
completion:^(BOOL finished){
[UIView animateWithDuration:0
delay:0
options:UIViewAnimationOptionCurveEaseIn
animations:^{
[self.tabsView removeFromSuperview];
//...
}
completion:^(BOOL finished){
NSLog(#"tabsView removed from Superview. Objects Deleted.");
[self showTabs];
NSLog(#"TabDelete finished. Button removed and tabsView updated accordingly.");
}
];
}];
And this is the showTabs method which was already called when I started the app:
-(void)showTabs {
NSLog(#"showTabs started...");
currentView = #"Tabs";
if ([tabsClippedView isDescendantOfView:self.view]) {
NSLog(#"There is already a tabsClippedView.");
} else {
NSLog(#"There is no tabsClippedView. I'll add one...");
[self initTabsClippedView];
}
if ([tabsView isDescendantOfView:tabsClippedView]) {
NSLog(#"There is already a tabsView");
} else {
NSLog(#"There is no tabsView for the buttons. I'll add one including buttons.");
[self initTabs];
}
Is it possible that you are getting EXC_BAD_ACCESS? Is it possible that the app is crashing because tabsView is deallocated when you send isDescendantOfView: to it. If you run with breakpoints enabled it should tell you the reason for the crash. If it is an EXC_BAD_ACCESS problem you should try NSZombie.
To activate NSZombie do the following:
Get info of the executable.
Go to the arguments tab.
In the "Variables to be set in the environment:" section add:
Name: NSZombieEnabled
Value: YES
Then run your app as usual and when it crashes it should tell you which deallocated object received what message.
EDIT: Just saw your edit. I think I nailed it. You're autoreleasing the views when you create them, so when they are removed from their superviews they are no longer retained and thus deallocated. You're app crashes because you're trying to run methods on deallocated views.
EDIT 2: Thought I should tell you that there is a better solution than the one posted by Praveen S.
Change your code as follows:
[tabsClippedView release];
tabsClippedView = [[UIView alloc] initWithFrame:tabsClippedFrame];
and
[tabsView release];
tabsView = [[UIView alloc] initWithFrame:tabsFrame];
The above code does the same thing as the code posted by Praveen S, but without the autorelease. An autorelease is more expensive than a regular release and should only be used when needed and in this case it isn't.
Rather than releasing before you allocate a new view you probably want to release the view when you're done with it:
[tabsView removeFromSuperview];
[tabsView release];
tabsView = nil;
or simply
[tabsView removeFromSuperview];
self.tabsView = nil;
and then instead of:
if ([tabsView isDescendantOfView:tabsClippedView]) ...
you can use:
if (tabsView) ...
As you might have noticed, there really is no need for you to retain the view. You could just as well do the following:
tabsView = [[UIView alloc] initWithFrame:tabsFrame];
[tabsClippedView addSubview:tabsView]; // This retains tabsView
[tabsView release];
and then to remove the view you would use:
[tabsView removeFromSuperview]; // This will release the tabsView
tabsView = nil;
Also remember to set the views to nil in viewDidUnload.
EDIT 3: Why self made such a difference:
What you need to understand is how properties and reference counting works. There are books and such you could read about it. I'm sure Google can provide you with some good references as well.
The difference between
self.tabsView = [[UIView alloc] initWithFrame:frame];
and
tabsView = [[UIView alloc] initWithFrame:frame];
is that self.tabsView is accessing the properties setter, while tabsView is accessing the instance variable directly.
A nonatomic, retain property's implementation looks something like the following:
- (void)setTabsView:(UIView *)view
{
if (view != tabsView) {
[tabsView release];
tabsView = [view retain];
}
}
So the property is taking care of the memory management for you. In my solution I take care of the memory management myself and thus I don't need the property to do it for me, so I don't use it.
I hope this explains why self made such a difference.
Change your code as follows:
self.tabsClippedView = [[[UIView alloc] initWithFrame:tabsClippedFrame] autorelease];
and
self.tabsView = [[[UIView alloc] initWithFrame:tabsFrame] autorelease];
I have been trying to implement Game Center multiplayer and struggling to get anything beyond a GKMatchViewController.
I have two views, one is my main menu, this is where multiplayer is launched and the player gets the GKMatchViewController. Then behind this, the user doesn't know it but the view changes to the multiplayer view where they actually play so when the GKMatchViewController is dismissed they are in the game view rather than menu.
Here I launch multiplayer (yes I am using cocos2d):
-(void)mpGo:(id)sender{
GKMatchRequest *request = [[[GKMatchRequest alloc] init] autorelease];
request.minPlayers = 2;
request.maxPlayers = 2;
[[GKMatchmaker sharedMatchmaker] findMatchForRequest:request
withCompletionHandler:^(GKMatch *returnedMatch, NSError *error)
{
if (error) NSLog(#"match error: %#", error);
else if (returnedMatch != nil)
{
match = [returnedMatch retain];
match.delegate = self; // start!
}
}];
tempVC = [[UIViewController alloc] init];
GKMatchmakerViewController *mmvc = [[[GKMatchmakerViewController alloc] initWithMatchRequest:request] autorelease];
if (mmvc != nil)
{
mmvc.matchmakerDelegate = self;
[[[CCDirector sharedDirector] openGLView] addSubview:tempVC.view];
[tempVC presentModalViewController:mmvc animated: YES];
}
[[CCDirector sharedDirector] replaceScene:[CCTransitionFade transitionWithDuration:1.0 scene:[mpView node] withColor:ccWHITE]];
}
When pressing 'Play Now' I get this error:
match error: Error Domain=GKErrorDomain Code=2 "The requested operation has been cancelled." UserInfo=0x2248d0 {NSLocalizedDescription=The requested operation has been cancelled.}
Then it just stays on that 'Finding Players...' view.
I never done something with GameCenter but maybe I can help anyway. When I get the error
"The requested operation has been
cancelled"
in reverse geocoder I have figured out, that the app had not the time to perform the request.
You get the error by executing findMatchForRequest:, so maybe your request variable is not set completely, check that. You can also try to put a NSLog() in first line (in the block) and look in your passed variable returnedMatch and error