Can someone tell me why this code isn't removing the subview that's being animated? The views are stacking up and the second time I dismiss the view, it won't animate and there are now two instances of subView with tag 99. It wasn't released the first time.
for (UIView *subview in [self.smallView subviews]) {
NSLog(#"view:%#",subview);
if (subview.tag == 99) {
[UIView animateWithDuration:.5
animations:^{
subview.alpha = 0;
}
completion:^(BOOL finished){
subview.alpha = 0;
[subview removeFromSuperview];
}];
}
}
Here's how I add the subview, viewVC is a ViewController Subclass and smallView is where I add my map and is the container for all the views.
#property (nonatomic, retain) CustomViewController *viewVC;
viewVC = [self.storyboard instantiateViewControllerWithIdentifier:#"VIEWVC"];
viewVC.view.frame = CGRectMake(20, 20, 280, 371);
viewVC.view.layer.cornerRadius = 15;
viewVC.view.tag = 99;
viewVC.view.alpha = 0;
[self.smallView addSubview:viewVC.view];
[UIView animateWithDuration:.5
animations:^{
viewVC.view.alpha = 1;
}
completion:^(BOOL finished){
viewVC.view.alpha = 1;
}];
I think the problem might be that you're removing an object from the array you're iterating through, which will cause problems. Try this:
NSArray *subviews = [NSArray arrayWithArray:[self.smallView subviews]];
for (UIView *subview in subviews) {
NSLog(#"view:%#",subview);
if (subview.tag == 99) {
[UIView animateWithDuration:.5
animations:^{
subview.alpha = 0;
}
completion:^(BOOL finished){
subview.alpha = 0;
[subview removeFromSuperview];
}];
}
}
This way, you are no longer removing an object from the array you are iterating through.
Could you please clarify why you are checking for the value of subview.tag to be 99? Is it at all possible that the code in the if statement isn't getting called at all? Throw in some NSLog calls, and see if your code within that if statement is even executing. If it is, then you should check out the methods that you are invoking within the if statement and make sure that they are all okay.
Related
I have a subview named courseOptionsView that I want to show and hide with buttons and under certain conditions. When I use a swipe gesture the view works and is positioned correctly, but when I make the exact same call to the function with another button it repositions it, but it repositions it to the original position it was in instead of the coordinates I'm sending. :/ I'm stumped.
*edit
It seems that when I ++ or -- from cHNumber it resets subview. Any ideas?
IBOutlet UIView *courseOptionsView;
- (IBAction)nextHole:(id)sender;
- (IBAction)showCourseOptionsView:(id)sender;
- (IBAction)hideCourseOptionsView:(id)sender;
//show and hide functions
-(void) showCourseOptions{
if(activeRound){
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[UIView setAnimationDuration:1.0];
[courseOptionsView setFrame:CGRectMake(50, 44, 200, 455)];
}
}
-(void) hideCourseOptions{
if(activeRound){
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[UIView setAnimationDuration:1.0];
[courseOptionsView setFrame:CGRectMake(-200, 44, 200, 455)];
}
}
// swipe calls
- (IBAction)showCourseOptionsView:(id)sender {
[self showCourseOptions];
}
- (IBAction)hideCourseOptionsView:(id)sender {
[self hideCourseOptions];
}
//button call
- (IBAction)nextHole:(id)sender
{
int totalStrokes = 0;
int totalPlusMinus = 0;
//[courseOptionsView setFrame:CGRectMake(0, 44, 200, 455)];
//[self showCourseOptions];
//save current hole
if(cHStrokes != [[currentRound objectForKey:[NSString stringWithFormat:#"hole%i",cHNumber]] intValue] ||
cHPutts != [[currentRound objectForKey:[NSString stringWithFormat:#"hole%i_putts",cHNumber]] intValue]){
[self saveCurrentHole];
}
// calculate and display +/- and strokes
for (int i = 0; i < ([currentRound count]/2); i++){
totalStrokes += [[currentRound objectForKey:[NSString stringWithFormat:#"hole%i",i+1]] intValue];
totalPlusMinus += [[[selectedScoreCardData objectAtIndex:0] objectForKey:[NSString stringWithFormat:#"hole%i_par",i+1]] intValue];
}
totalPlusMinus = totalStrokes - totalPlusMinus;
self.totalStrokesLabel.text = [NSString stringWithFormat:#"%i", totalStrokes];
self.totalPlusMinusLabel.text = [NSString stringWithFormat:#"%i",totalPlusMinus];
//set to next hole
/*-------------------
| SHOW VIEW HERE
--------------------*/
if(cHNumber == 17){
self.nextHoleButton.enabled = NO;
[self showCourseOptions];
}
if(cHNumber == 1){
self.prevHoleButton.enabled = YES;
}
cHNumber ++;
self.cHNumberLabel.text = [NSString stringWithFormat:#"%i", cHNumber];
[self populateScoreCard];
[strokePickerView reloadAllComponents];
if(([currentRound count]/2) < cHNumber){
[strokePickerView selectRow:cHStrokes - 1 inComponent:0 animated:YES];
[strokePickerView selectRow:0 inComponent:1 animated:YES];
}else{
[strokePickerView selectRow:[[currentRound objectForKey:[NSString stringWithFormat:#"hole%i",cHNumber]] intValue] - 1 inComponent:0 animated:YES];
[strokePickerView selectRow:[[currentRound objectForKey:[NSString stringWithFormat:#"hole%i_putts",cHNumber]] intValue] inComponent:1 animated:YES];
}
}
I think that maybe after you position the view you are calling another function that changes the layout again. A possible candidate is populateScoreCard.
I have a UIViewContoller with a few container(child) UIiewsController. The main UIViewController (mvc) contains:
1.) A UICollectionView that occupies the entire view of mvc (it is above mvc.view but below all other controls).
2.) A UIViewController that displays search options (s1vc)
3.) another similar to #2 (s2vc)
4.) another similar to #2 (s3vc)
I have added gesture recognizers to mvc so that a user can hide/show each of the child view controllers by swiping them off of the screen.
The problem is that when a user swipes any of the svcs off of the screen, they cannot scroll the mvc's collectionView.
Here is how I am hiding/showing the svcs:
-(void)swipeLeftGestureHandler:(UIGestureRecognizer*)gestureRecognizer{
SMLOG(#"Swiped Left");
if([SMUser activeUser] == nil) return;
if([self gestureHorizontalScreenSide:gestureRecognizer] == kHorizontalScreenSideLeft){
[self hideFacets];
}
else{
[self showAccordion];
}
}
-(void)swipeRightGestureHandler:(UIGestureRecognizer*)gestureRecognizer{
SMLOG(#"Swiped Right");
if([SMUser activeUser] == nil) return;
if([self gestureHorizontalScreenSide:gestureRecognizer] == kHorizontalScreenSideLeft){
[self showFacets];
}
else{
[self hideAccordion];
}
}
-(void)hideFacets{
if(self.facetVisible == NO) return;
[UIView animateWithDuration:0.25
animations:^{
CGRect newFrame = self.facetViewController.view.frame;
newFrame.origin = CGPointMake(newFrame.origin.x - newFrame.size.width, newFrame.origin.y);
self.facetViewController.view.frame = newFrame;
self.facetVisible = NO;
}
completion:^(BOOL finished){
self.facetViewController.view.hidden = YES;
self.facetViewController.view.userInteractionEnabled = NO;
}];
}
-(void)showFacets{
if([SMUser activeUser] == nil) return;
if(self.facetVisible == YES) return;
self.facetViewController.view.userInteractionEnabled = YES;
[UIView animateWithDuration:0.25
animations:^{
self.facetViewController.view.hidden = NO;
CGRect newFrame = self.facetViewController.view.frame;
newFrame.origin = CGPointMake(newFrame.origin.x + newFrame.size.width, newFrame.origin.y);
self.facetViewController.view.frame = newFrame;
self.facetVisible = YES;
}
completion:^(BOOL finished){
}];
}
As you can see I'm toggling the svc.view.hidden property and then I also tried toggling th svc.userInteractionEnabled.property but no luck. Still cannot swipe the collection view by swiping where the facets view controller was/is.
Any ideas?
The solution here is to add another outlet (using IB) for the container view in the parentViewController (I called it facetContainerView in this code) and then set it's userInteractionEnabled property.
-(void)hideFacets{
if(self.facetVisible == NO) return;
self.facetVisible = NO;
[UIView animateWithDuration:0.25
animations:^{
CGRect newFrame = self.facetViewController.view.frame;
newFrame.origin = CGPointMake(newFrame.origin.x - newFrame.size.width, newFrame.origin.y);
self.facetViewController.view.frame = newFrame;
}
completion:^(BOOL finished){
self.facetViewController.view.hidden = YES;
self.facetContainerView.userInteractionEnabled = NO;
}];
}
-(void)showFacets{
if(self.facetVisible == YES) return;
self.facetVisible = YES;
self.facetContainerView.userInteractionEnabled = YES;
[UIView animateWithDuration:0.25
animations:^{
self.facetViewController.view.hidden = NO;
CGRect newFrame = self.facetViewController.view.frame;
newFrame.origin = CGPointMake(newFrame.origin.x + newFrame.size.width, newFrame.origin.y);
self.facetViewController.view.frame = newFrame;
}
completion:^(BOOL finished){
}];
}
I was curious as to how this new view outlet was different from self.facetViewController.view, so I inserted this code to compare addresses (they are different indeed). I'm not sure about the heirarchy, but it seems that there is an extra view layer that I was not aware of.
NSLog(#"self.facetViewController.view: %p", self.facetViewController.view);
NSLog(#"self.facetContainerView: %p", self.facetContainerView);
Hopefully this will help someone at some point.
I have a method that gets called if a video upload to Facebook has failed. If that method is called then I would like for a UILabel to briefly appear in any view controller that a user happens to be on at the time the upload fails.
Is this possible?
I asked a similar question earlier about a UIAlertView, but I realized that there are certain circumstances under which an alert could negatively impact user experience.
You can do this is many ways -
1) you can add UILabel to your applications main Window.
2) if you are using a UINavigationController then you get the instance of current viewcontroller and then can add UILabel to its view.
3) if you are using a UITabBarController in this case you can also get the instance of current viewcontroller by accessing tabBarController's selected viewcontroller.
this code Im posting below is from HackBook sample app from facebook. they done similar to what you want.
- (void)showMessage:(NSString *)message {
CGRect labelFrame = messageView.frame;
labelFrame.origin.y = [UIScreen mainScreen].bounds.size.height - self.navigationController.navigationBar.frame.size.height - 20;
messageView.frame = labelFrame;
messageLabel.text = message;
messageView.hidden = NO;
// Use animation to show the message from the bottom then
// hide it.
[UIView animateWithDuration:0.5
delay:1.0
options: UIViewAnimationCurveEaseOut
animations:^{
CGRect labelFrame = messageView.frame;
labelFrame.origin.y -= labelFrame.size.height;
messageView.frame = labelFrame;
}
completion:^(BOOL finished){
if (finished) {
[UIView animateWithDuration:0.5
delay:3.0
options: UIViewAnimationCurveEaseOut
animations:^{
CGRect labelFrame = messageView.frame;
labelFrame.origin.y += messageView.frame.size.height;
// UIView *messageView; declared in header
messageView.frame = labelFrame;
}
completion:^(BOOL finished){
if (finished) {
messageView.hidden = YES;
messageLabel.text = #"";
}
}];
}
}];
}
thank you for your time, (As a side note, this question pertains mostly to iOS 4.2.3, I am aware some of these issues could be resolved with moving the code base to iOS 5, however we would like to release this app for phones running iOS 4 as well.)
I have a "MasterViewController" that is in charge of calling and dismising other UIVIewControllers.
First we trigger a new one:
In MasterViewController.m
-(IBAction)triggerPrime:(id)sender {
[self clearHomeScreen];
NSUInteger randomNumber = arc4random() % 2;
if (randomNumber == 0) {
self.flashTextViewIsDisplayed = NO;
ThinkOfViewController *thinkVC = [[ThinkOfViewController alloc] initWithNibName:#"ThinkOfViewController" bundle:nil];
self.thinkOfViewController = thinkVC;
[thinkVC release];
self.picturePrimeViewIsDisplayed = YES;
[self.view addSubview:self.thinkOfViewController.view];
}
else if (randomNumber == 1) {
self.picturePrimeViewIsDisplayed = NO;
FlashTextPrimeViewController *flashVC = [[FlashTextPrimeViewController alloc] initWithNibName:#"FlashTextPrimeViewController" bundle:nil];
self.flashTextPrimeViewController = flashVC;
[flashVC release];
self.flashTextViewIsDisplayed = YES;
[self.view addSubview:self.flashTextPrimeViewController.view];
}
Let's say that our randomNumber is 0, and it adds the ThinkOfViewController to the subview, (This is a very basic screen, it essentially displays some text with some assets animating:
In ThinkOfViewController.m
- (void)viewDidLoad {
[super viewDidLoad];
self.thinkOf.alpha = 0.0;
self.dot1.alpha = 0.0;
self.dot2.alpha = 0.0;
self.dot3.alpha = 0.0;
self.background.alpha = 0.0;
[self animateViews];
}
-(void)animateViews {
[UIView animateWithDuration:0.25 animations:^ {
self.background.alpha = 1.0;
}completion:^(BOOL finished) {
[UIView animateWithDuration:0.75 delay:0.00 options:UIViewAnimationCurveEaseIn animations:^ {
self.thinkOf.alpha = 1.0;
}completion:^(BOOL finished) {
[UIView animateWithDuration:0.20 delay:0.60 options:UIViewAnimationCurveEaseIn animations:^ {
self.dot1.alpha = 1.0;
}completion:^(BOOL finsihed) {
[UIView animateWithDuration:0.20 delay:0.60 options:UIViewAnimationCurveEaseIn animations:^ {
self.dot2.alpha = 1.0;
}completion:^(BOOL finished) {
[UIView animateWithDuration:0.20 delay:0.60 options:UIViewAnimationCurveEaseIn animations:^ {
self.dot3.alpha = 1.0;
}completion:^(BOOL finished) {
[UIView animateWithDuration:0.50 delay:0.60 options:UIViewAnimationCurveEaseInOut animations:^{
self.view.alpha = 0.0;
}completion:^(BOOL finished) {
NSLog(#"all animations done");
[[NSNotificationCenter defaultCenter] postNotificationName:#"removeThinkOfView" object:nil];
}];
}];
}];
}];
}];
}];
}
As you can see, once the animation sequence is finished, I post a notification to NSNotificationCenter (which resides in the MasterViewController) to remove this viewController.
In MasterViewController.m
-(void)removeThinkOfView {
[self.thinkOfViewController.view removeFromSuperview];
[self showPicturePrime];
}
-(void)showPicturePrime {
if (self.picturePrimeViewController == nil) {
PicturePrimeViewController *pVC = [[PicturePrimeViewController alloc] initWithNibName:#"PicturePrimeViewController" bundle:nil];
self.picturePrimeViewController = pVC;
[pVC release];
[self.view addSubview:self.picturePrimeViewController.view];
}
else {
PicturePrimeViewController *pVC = [[PicturePrimeViewController alloc] initWithNibName:#"PicturePrimeViewController" bundle:nil];
self.picturePrimeViewController = pVC;
[pVC release];
[self.view addSubview:self.picturePrimeViewController.view];
}
}
Now a picturePrimeViewController is loaded and added to the subview, everything loads and displays fine. Now, to get a new prime, you simple swipe for a new one.
In picturePrimeViewController.m
-(void)handleSwipeFromRight:(UISwipeGestureRecognizer *)gestureRecognizer {
if (!transitioning) {
[self performTransition];
}
}
-(void)performTransition {
CATransition *transition = [CATransition animation];
transition.duration = 1.0;
transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
transition.type = kCATransitionPush;
transition.subtype = kCATransitionFromLeft;
transitioning = YES;
transition.delegate = self;
[self.view.layer addAnimation:transition forKey:nil];
[UIView animateWithDuration:0.5 animations:^ {
self.view.alpha = 0.0;
}completion:^(BOOL finished) {
NSLog(#"Transition Animation Complete");
}];
}
-(void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag {
transitioning = NO;
[[NSNotificationCenter defaultCenter] postNotificationName:#"nextPrime" object:nil];
}
Now in the animationDidStop: Method, i again post another notification to the NSNotificationCenter back to the MasterViewController to signal another prime.
In MasterViewController.m
-(void)nextPrime {
if (self.picturePrimeViewIsDisplayed) {
[self.picturePrimeViewController.view removeFromSuperview];
self.picturePrimeViewController = nil;
[self showAPrime];
}
if (self.flashTextViewIsDisplayed) {
[self.flashTextPrimeViewController.view removeFromSuperview];
self.flashTextPrimeViewController = nil;
[self showAPrime];
}
}
However! Upon swiping the right, the view animates properly but then I get a bad access crash when the ThinkOfViewController is attempting to dealloc it's UIViews. So for some reason, it is taking that the ThinkOfViewController a long time to dealloc, when I assumed when I called [self.thinkOfViewController removeFromSuperview], it should of been removed immeditately.
(Side note, the textFlashViewController has no problems, the only problems are comming with this ThinkOfViewController).
Is this paradigm I set up a bad implmentation of dealing with UiViewController's comming in and out? I have read that delegation can help in this instance, I'm just not sure how that works.
If any of you have any ideas, I would be so grateful, as I have mined through forums and documentations and cannot see a solution to my rather custom implmentation of dealing with these views.
So, the short answer is yes, this "paradigm" is a bad implementation of dealing with UIViewControllers. I would suggest going and reading Apple's View Controller Programming Guide, which outlines the correct implementation, but here's a quick synopsis:
Your MasterViewController, from what I gather, manages these other two UIViewControllers, ThinkOfViewController and PicturePrimeViewController. When you are adding or removing the views of a view controller from the screen, you don't add or remove the views to the MasterViewController. The whole point of a view controller is to manage the "showing" or "hiding" of its view. I put showing and hiding in quotes because you're not actually showing or hiding them. You are pushing and popping them off of the view hierarchy, and this is done with a UINavigationController. Each UIViewController knows the UINavigationController that it is being controlled by (thus, UINavigationControllers are known as "controllers of controllers"), and you can thus push a view controller onto the stack by saying
[self.navigationController pushViewController:vcToBePushed animated:YES];
When the view controller that is on top of the stack is to be removed, you simply have to say
[self.navigationController popViewControllerAnimated:YES];
Pushing or popping a UIViewController onto or off of the stack means the view controller takes its view with it, and displays it or removes it from on screen. So that covers, in the tightest nutshell imaginable, UIViewController view management.
The other issue, regarding the EXC_BAD_ACESS crash, means that you are trying to access memory that has already been allocated for another object. I suspect the culprit is here:
if (randomNumber == 0) {
self.flashTextViewIsDisplayed = NO;
ThinkOfViewController *thinkVC = [[ThinkOfViewController alloc] initWithNibName:#"ThinkOfViewController" bundle:nil];
self.thinkOfViewController = thinkVC;
[thinkVC release];
self.picturePrimeViewIsDisplayed = YES;
[self.view addSubview:self.thinkOfViewController.view];
}
The problem is that you release thinkVC before it has had a chance to be retained by the MasterViewController's view (which happens in self.view addSubview:). Thus, self.view is adding a pointer to an object whose memory just got added back to the heap. Scrapping your add/removeSubview methodology for the push/pop methodology I just outlined will keep that sort of memory issue from happening.
I hope this helps, but let us (us being SO) know if you still have issues.
I have a strange problem with a UITextField - I am setting the value of it to #"" (or anything else still causes it) and then shrinking the field with to zero. Next time I unshrink it, it displays the same value it had before I shrunk it, regardless of my having changed the text in the view. Typing in the field causes the glitch to go away, but it looks bad.
Complete code to reproduce:
throwaway_testViewController.h:
#import <UIKit/UIKit.h>
#interface throwaway_testViewController : UIViewController <UITextFieldDelegate>{
UITextField * unitField;
}
#property (nonatomic, assign) IBOutlet UITextField * unitField;
-(IBAction)setEditingSectionUnits;
-(void)setEditingSectionValue;
-(IBAction)equalsPress;
#end
throwaway_testViewController.m
#import "throwaway_testViewController.h"
#implementation throwaway_testViewController
#synthesize unitField;
#pragma mark - Inputs
-(IBAction)equalsPress{
[UIView animateWithDuration:0.5 animations:^(void){
[unitField setText:#""];
[self setEditingSectionValue];
}];
}
#pragma mark Input Control
-(void)setEditingSectionUnits{
[UIView animateWithDuration:0.5 animations:^(void){
CGRect newRect = unitField.frame;
newRect.size.width = 160;
unitField.frame = newRect;
}completion:^(BOOL completed){
completed ? [unitField setNeedsDisplay] : nil;
}];
[unitField becomeFirstResponder];
}
-(void)setEditingSectionValue{
[UIView animateWithDuration:0.5 animations:^(void){
CGRect newRect = unitField.frame;
newRect.size.width = [unitField.text sizeWithFont:unitField.font constrainedToSize:CGSizeMake(80, 250)].width;;
unitField.frame = newRect;
}completion:^(BOOL completed){
completed ? [unitField setNeedsDisplay] : nil;
}];
[unitField resignFirstResponder];
}
- (BOOL)textFieldShouldReturn:(UITextField *)textField{
[self setEditingSectionValue];
return TRUE;
}
-(void)textFieldDidBeginEditing:(UITextField *)textField{
[self setEditingSectionUnits];
}
#end
In the xib, place a UITextField tied to unitField, and set the delegate of the text field to be file's owner.
Place a UIButton labeled equalsPress and tie it to that IBAction, and another called edit, tied to setEditingSectionUnits. To see the bug reproduced:
Run the app
Press edit
type something into the text field (min 8-10 characters)
press enter on the keyboard
press equalsPress
press edit
Should see: cursor and empty text field
Actually see: whatever you typed last, with a cursor at the start.
Typing makes this text disappear.
I am having trouble replicating your issue I have mocked this up to use the same sort of mechanisms as you and it works as expected.
- (void)viewDidLoad
{
[super viewDidLoad];
[self.unitField setText:#"Testing"];
}
- (IBAction)hideButtonTapped:(id)sender
{
[UIView animateWithDuration:0.5 animations:^(void)
{
[self.unitField setText:#""];
self.unitField.frame = CGRectMake(10, 10, 0, 30);
}
];
}
- (IBAction)showButtonTapped:(id)sender
{
[UIView animateWithDuration:0.5 animations:^(void)
{
self.unitField.frame = CGRectMake(10, 10, 100, 30);
}
];
}
Perhaps you can give more of your code as I feel you may have omitted something important or you can point out where you implementation differs to the above.
after replicating your code, i found that the problem you mentioned occurs only if you press return on the keyboard and then tap the equals press button...
resigning first responder before calling animation block in your setEditingSectionValue method resolved the problem:
-(void)setEditingSectionValue{
[unitField resignFirstResponder];
[UIView animateWithDuration:0.5 animations:^(void){
CGRect newRect = unitField.frame;
newRect.size.width = [unitField.text sizeWithFont:unitField.font constrainedToSize:CGSizeMake(80, 500)].width;;
unitField.frame = newRect;
}completion:^(BOOL completed){
completed ? [unitField setNeedsDisplay] : nil;
}];
}
I came up with a solution to your problem, its not nice, but at least its working :D
-(IBAction)equalsPress{
tmp = nil;
//same code as yours
}
-(void)setEditingSectionUnits{
if (tmp != nil) {
unitField.text = tmp;
}
//same code as yours
}
-(void)setEditingSectionValue{
if ([unitField.text length] > 9) {
tmp = unitField.text;
unitField.text = [unitField.text substringToIndex:9];
[tmp retain];
}else {
tmp = nil;
}
//same code as yours
}
I declared tmp in the header file:
NSString *tmp;
I had a quick look but can you try retaining UITextField:
#property (nonatomic, retain) IBOutlet UITextField * unitField;
Also equalsPress can be
-(IBAction)equalsPress{
[unitField setText:#""];
[UIView animateWithDuration:0.5 animations:^(void){
[self setEditingSectionValue];
}];
}