Reusing an UIView - iphone

I have a UIView that displays an image depending on how well the user did on a level, then the user has the options to continue to the next level and once they are finished with that one the UIView displays again with an image depending on how well they did
I release the UIView after the user decides they want to go to the next level and inside the UIView I'm pretty sure I release everything once I'm finished with it but when the UIView is loaded for the second time the image from the first time is still there and the second image gets put on top of it so you see both images at the same time.
I'm not sure why this is happening like I said I'm pretty sure I release everything inside the UIView and then I release the UIView when the user is finished with it
I created the UIView with Interface Builder
Any help would be appreciated
//this is the code to access the UIView
-(void)DisplayStatsForLevel:(NSInteger)level ScoreEarned:(NSInteger)pScore NumberHit:(NSInteger)pNumberHit TotalTargets:(NSInteger)pTotalTargets MedalEarned:(NSInteger)pMedalEarned BulletsFired:(NSInteger)pBulletsFired
{
switch(level)
{
case 1:
[levelOne removeFromSuperview];
[levelOne release]; levelOne = nil;
[self.view addSubview:levelComplete];
[levelComplete SetupScreen:pScore NumberHit:pNumberHit TotalTargets:pTotalTargets MedalEarned:pMedalEarned BulletsFired:pBulletsFired];
break;
case 2:
[levelTwo removeFromSuperview];
[levelTwo release]; levelTwo = nil;
[self.view addSubview:levelComplete];
[levelComplete SetupScreen:pScore NumberHit:pNumberHit TotalTargets:pTotalTargets MedalEarned:pMedalEarned BulletsFired:pBulletsFired];
break;
default:
break;
}
}
//this is the code that releases the UIView
-(void)NextLevel:(NSInteger)nextLevel
{
switch (nextLevel)
{
case 2:
[levelComplete removeFromSuperview];
[levelComplete release]; levelComplete = nil;
[self.view addSubview:levelTwo];
[levelTwo SetupLevel];
break;
default:
break;
}
}
//this is the code that displays the image
switch (medalWon)
{
case 1:
medalImage = [UIImage imageNamed:#"Bronze.png"];
break;
case 2:
medalImage = [UIImage imageNamed:#"Silver.png"];
break;
case 3:
medalImage = [UIImage imageNamed:#"Gold.png"];
break;
case 4:
medalImage = [UIImage imageNamed:#"Platinum.png"];
break;
default:
break;
}
medal =[[UIImageView alloc] initWithFrame:medalFrame];
medal.image = medalImage;
[medalImage release];
[medal setNeedsDisplay];
[self addSubview:medal];

I tried something similar just now, and in order to get two overlapping images, I had to call this twice:
medal =[[UIImageView alloc] initWithFrame:medalFrame];
medal.image = medalImage;
[medalImage release];
[medal setNeedsDisplay];
[self addSubview:medal];
Can you check and see if you are doing that?

Related

simulate button pressed by single tap

as titled, would like to tap the screen, screen will be a bit dark translucent, while the finger leaves the screen, the screen turn back to normal, which is similar as UIButton does.
In this case, I know UIButton is much easier, but here is just a sample
my codes as the following:
- (IBAction)tapped:(UITapGestureRecognizer *)sender
{
UIView * tintView = [[UIView alloc] initWithFrame:[UIScreen mainScreen].bounds];
[self.view addSubview:tintView];
switch (sender.state) {
case UIGestureRecognizerStateRecognized: {
[tintView setBackgroundColor:[UIColor colorWithRed:.25 green:.25 blue:.25 alpha:.5]];
NSLog(#"begin");
}
default: {
[tintView setBackgroundColor:[UIColor clearColor]];
NSLog(#"ended");
}
}
}
But, when tapping the screen, it won't be changed as above codes looks, although begin and ended are captured in the console.
If interchanging those codes between case and default like
switch (sender.state) {
case UIGestureRecognizerStateRecognized: {
[tintView setBackgroundColor:[UIColor clearColor]];
NSLog(#"begin");
}
default: {
[tintView setBackgroundColor:[UIColor colorWithRed:.25 green:.25 blue:.25 alpha:.5]];
NSLog(#"ended");
}
}
begin and ended could be shown in the console, but the screen will be darker and darker when tapping, never back to normal, clear color.
What's wrong with my codes? How to make it happen?
Thanks!
[self performSelector: withObject: afterDelay:] would be the usual way to accomplish something like this.
You'd setup your method which dismisses the darkened view separately, then reference it as the selector
[self performSelector:#selector(methodWhichDismissesDarkView:) withObject:nil afterDelay:0.2]
call this immediately after you darken the view, and 0.2 seconds later it will fire.
To really do it right though, make sure you can gracefully handle interruptions that happen during the delay by using this:
[NSObject cancelPreviousPerformRequestsWithTarget:self];
Which will cancel the pending action when the app no longer needs to handle it.
by following #ryancumley 's hint, below is updated codes.
#property (strong, nonatomic) UIView * tintView;
#synthesize tintView;
- (IBAction)tapped:(UITapGestureRecognizer *)sender
{
switch (sender.state) {
case UIGestureRecognizerStateRecognized: {
[self.tintView setBackgroundColor:[UIColor colorWithWhite:0.000 alpha:0.150]];
[self performSelector:#selector(setTintView:) withObject:nil afterDelay:.25];
NSLog(#"begin");
}
default: {
break;
NSLog(#"ended");
}
}
}
- (void)setTintView:(UIView *)tintView
{
[self.tintView setBackgroundColor:[UIColor colorWithRed:.25 green:.25 blue:.25 alpha:0]];
}
It works finally and looks like tapping a button. But, NSLog(#"ended") not triggered.
Why my original can't work?
Is this updated a correct approach or workaround?

Layer appearing with a delay

I have an iPhone game in which players can tweet their score.
I use TWTweetComposeViewController for this.
Since the Twitter sheet can take some time to load, I would like a "loading..." layer to show up after the player has clicked the tweet button, while waiting for the Twitter sheet to show up.
My problem is that the layer (named "colcol" in the code below) only shows up once the tweet sheet is ready! It's as if the layer waited for the tweet sheet to be ready to show up. Which is definitely not what I expect.
Any idea why ?
Thank you!
Here is the tweetScore function, called when the user touches a CCMenuItemImage:
- (void) tweetScore: (CCMenuItem *) menuItem {
colcol=[CCLayerColor layerWithColor:ccc4(0,0, 0, 200) width:50 height:50];
colcol.position=ccp(winSize.width/2-25,winSize.height/2-25);
[self addChild:colcol z:15];
if ([TWTweetComposeViewController canSendTweet])
{
TWTweetComposeViewController *tweetSheet = [[TWTweetComposeViewController alloc] init];
[tweetSheet setInitialText:[NSString stringWithFormat:#"I scored %d!", playerScore]];
tweetSheet.completionHandler = ^(TWTweetComposeViewControllerResult result) {
[appDelegate.viewController dismissModalViewControllerAnimated:YES];
switch (result) {
case TWTweetComposeViewControllerResultCancelled:
[self removeChild:colcol cleanup:YES];
break;
case TWTweetComposeViewControllerResultDone:
[[GCHelper sharedInstance] reportAchievementIdentifier:#"tweet_score" percentComplete:100.0];
[self removeChild:colcol cleanup:YES];
break;
default:
[self removeChild:colcol cleanup:YES];
break;
}
};
[appDelegate.viewController presentModalViewController:tweetSheet animated:YES];
}
else
{
// handle this case
}
}
In general, screen updates in CoreAnimation, UIKit, and OS X are deferred until the end of the current pass through the run loop. You are adding the CALayer, then doing some time-consuming work (setting up the TWTweetComposeViewController), then returning so the run loop can finish -- so there's no time for a screen update to happen in between.
Try setting up the TWTweetComposeViewController in a separate pass through the run loop, using dispatch_async(dispatch_get_main_queue(), ^{ /* your code here */ }).

Problem with removing a subview and then adding it again

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];

Hiding or moving SegmentContoller

Hello I've tried for 3 weeks to solve this issue and it stumps me. What i am trying to do is create a 3 part segment from an array, display it in a view in a certain position, then remove it from view when the "OFF" flag is set. Every thing works except the removal of the segment. It will even commuticate with (pickOne) and display the segment letters in a label. What i can't get to work is either of the two: setHidden:YES, or removeAllSegments. Any help would be appreciated. Here is my code.
- (void) showSegment {
int x = 192;
int y = 212;
int w = 125;
int h = 25;
SegUnit1 = #"A";
SegUnit2 = #"B";
SegUnit3 = #"C";
threeSegs = [NSArray arrayWithObjects: SegUnit1, SegUnit2, SegUnit3, nil];
segSize = [NSArray arrayWithArray:threeSegs];
UISegmentedControl *heightSC = [[UISegmentedControl alloc] initWithItems:segSize];
if ([segmentState_height isEqualToString:#"ON"]) {
NSLog(#"segmentState_height = %#",segmentState_height);
heightSC.frame = CGRectMake(x, y, w, h);
heightSC.segmentedControlStyle = UISegmentedControlStyleBar;
heightSC.selectedSegmentIndex = -1;
[heightSC addTarget:self
action:#selector(pickOne:)
forControlEvents:UIControlEventValueChanged];
[self.view addSubview:heightSC];
[heightSC release];
} else if ([segmentState_height isEqualToString:#"OFF"]) {
NSLog(#"segmentState_height = %#",segmentState_height);
[heightSC setHidden:YES]; // NSLog showing "OFF" but segment will not hide.
[heightSC removeAllSegments]; // NSLog showing "OFF" and segment is suppose to dismantle and does not.
}
}
I know now that i have to "not" create and remove in the same function, and was given a tip on correcting this but I don't know how to use the tip.
here is what was suggested.
Well, your method is a little confused, since you are trying to both create and hide at the same time. So you might consider splitting that up into separate methods.
In general, it will be along these lines:
Code:
if ([self theControlProperty] == nil)
{
UISeg... *theControl = [[UISeg alloc] ....];
[self setTheControlProperty:theControl];
...
}
if (shouldHideTheControl)
{
[[self theControlProperty] setHidden:YES];
}
Any help would be appreciated.
The problem you have is that you're creating a new UISegmentedControl instance every time that method is called. The first time through, you create an instance and add it as a subview to your view. This apparently works fine, as it should. Then the method returns, and you no longer have any easy way to refer to that instance that you created. When you re-enter -showSegment, you create a different instance, and then hide and/or destroy it. This different instance has no effect whatsoever on the instance that you gave to the view.
What you need to do is make heightSC an instance variable. Add it to the interface declaration in the header file, then initialize it only once, and hide or modify it as needed subsequently. The key point is that you need to have a reference to the instance of the UISegmentedControl which is being drawn on the screen, a reference that lives outside the method itself that you can use the second, third, fourth, etc time you call that method.
Try using the remove segments in your button choice method pickOne. This takes it outside the showSegment method and matches the users desired action to make the change and clear off the buttons.
- (void) pickOne:(id)sender {
UISegmentedControl* userChose = sender;
if( [userChose selectedSegmentIndex] == 0 ){
your first button operation;
[heightSC removeAllSegments];
}
if( [userChose selectedSegmentIndex] == 1 ){
your second button operation;
[heightSC removeAllSegments];
}
if( [userChose selectedSegmentIndex] == 2 ){
your third button operation;
[heightSC removeAllSegments];
}
}
I tried this and got the results I was looking for. Thanks goes to Mythogen and BrianSlick I just need to check and make sure there are no leaks. Now that will be a task.
Does anyone know if I need the second [heightSC release]; ?
// .h
# interface ------ {
UISegmentedControl *segmentPicked;
}
|
#property (nonatomic, retain) UISegmentedControl *segmentPicked;
// .m
|
#synthesize segmentPicked;
|
if ([self segmentPicked] == nil) {
UISegmentedControl *heightSC = [[UISegmentedControl alloc] initWithItems:segSize];
[self setSegmentPicked:heightSC];
[heightSC release];
heightSC.frame = CGRectMake(x, y, w, h);
heightSC.segmentedControlStyle = UISegmentedControlStyleBar;
heightSC.selectedSegmentIndex = -1;
[heightSC addTarget:self
action:#selector(pickOne:)
forControlEvents:UIControlEventValueChanged];
[self.view addSubview:heightSC];
[heightSC release];
}
if ([segmentState_height isEqualToString:#"OFF"])
{
[[self segmentPicked] setHidden:YES];
} else {
[[self segmentPicked] setHidden:NO];
}
[yourSegment removeFromSuperview];
?

Switch statement using a selectedSegmentIndex

How do to all,
This is the first time posting on the site, so i presume i have not followed all the
formats and protocols correctly.
My app has a UIView containing a list of questions, and a UISegmentedControl called "question_btn" for each question. The UISegmentedControl has 2 segments, the first for a NO answer, the 2nd for a YES answer, corresponding to each question.
Each "question_btn" individually calls -(void) processQuestions{}, but when i click on each "question_btn" only the last one is processed properly. All previous "question_btns" call the processQuestions method, but do not implement its code.
Im my problem with the UISegmentControl, or the switch statement, or both.
Any suggestions would be greatly appreciated.
The button is defined as follows
question_btn = [[[UISegmentedControl alloc] initWithFrame:CGRectMake(QUESTION_BUTTON_XPOS, questionsButton_YPos, 80, 20)] autorelease];
[question_btn insertSegmentWithTitle:#"No" atIndex:0 animated:NO];
[question_btn insertSegmentWithTitle:#"Yes" atIndex:1 animated:NO];
[question_btn setEnabled:YES forSegmentAtIndex:0];
[question_btn setEnabled:YES forSegmentAtIndex:1];
question_btn.segmentedControlStyle = UISegmentedControlStyleBar;
question_btn.tintColor = [UIColor colorWithRed:200.0/255.0 green:200.0/255.0 blue:200.0/255.0 alpha:1.0];
[question_btn addTarget:self action:#selector(processQuestions) forControlEvents:UIControlEventValueChanged];
Below is the processQuestions
- (void) processQuestions
{
//NSLog(#"QuestionsView processQuestions: Called");
//Process the questions i.e. determine whether yes or no was the answer
answersArray = [[NSMutableArray alloc] init];
int selectedIndex;
selectedIndex = [question_btn selectedSegmentIndex];
switch (selectedIndex)
{
case 0:
NSLog(#"QuestionsView processQuestions: No answered");
break;
case 1:
NSLog(#"QuestionsView processQuestions: Yes answered");
break;
}
[answersArray addObject:[NSNumber numberWithInt:selectedIndex]];
NSLog(#"QuestionsView processQuestions: answersArray contains: %#", answersArray);
}
Here is how i got the code to iterate properly and get the question_btn selected index value. It was just a matter of creating a mutable array to hold the question_btn objects,
and then set the selected index value for each button using a for loop.
The buttonsArray is set in my createView method.
buttonsArray = [[NSMutableArray alloc] init];
- (void) processQuestions
{
//NSLog(#"QuestionsView processQuestions: Called");
answersArray = [[NSMutableArray alloc] init];
int selectedIndex;
for (int i = 0; i < numQuestions; i ++)
{
question_btn = (UISegmentedControl*)[buttonsArray objectAtIndex:i];
selectedIndex = [question_btn selectedSegmentIndex];
switch (selectedIndex)
{
case 0:
NSLog(#"QuestionsView processQuestions: No answered for question: %d", i + 1);
break;
case 1:
NSLog(#"QuestionsView processQuestions: Yes answered for question: %d", i + 1);
break;
}
[answersArray addObject:[NSNumber numberWithInt:selectedIndex]];
}
NSLog(#"QuestionsView processQuestions: answersArray contains: %#", answersArray);
}
If you have a list of questions, you can use an array of Segemnted control. You may have a button, 'Get Result' so when pressed, you iterate through all the segmented controls and check there selected index (yes/no - 0/1).
right now, in your above code, processQuestions is called for each segmented control's state changing. If the default value is kept by the user, it will not add result to the answer array.