0

I have a method makeButtons (posted here), which is removing all buttons in the screen and adding them again. This works fine in viewDidLoad and viewDidAppear. I am accessing information from a webservice which is telling me I need a new button. When I am calling [self makebuttons] from that method, nothing happends, until I move forth and back with my NavigationController forcing viewDidAppear to do the work again. My question is why? I am doing exactly the same, unless it's not called from viewDidAppear, but from doneGettingInformation.

- (void) viewDidAppear:(bool) animated {
    [self makebuttons]; // Works great!
}

- (void) doneGettingInformation : (ASIFormDataRequest *) request {
    NSString *response = [request responseString];
    [[self.temp.userInfo objectForKey:@"spillider"] addObject:response];
    [self makebuttons]; // This gets called, but nothing changes in the view itself.
}

- (void) makeButtons {
    NSLog(@"kjort");
    int newAntall = [[self.temp.userInfo objectForKey:@"spillider"] count];
    for (UIButton * button in gameButtons) {
        NSString *tag = [NSString stringWithFormat:@"%i",button.tag];
        [button removeFromSuperview];
        if ([webviews objectForKey:tag]) {
            [[webviews objectForKey:tag] removeFromSuperview];
            [webviews removeObjectForKey:tag];
        }
    }
    [gameButtons removeAllObjects];
    scroller.contentSize = CGSizeMake(320, 480);
    if (newAntall > 3) {
        CGSize scrollContent = self.scroller.contentSize;
        scrollContent.height = scrollContent.height+((newAntall-3)*BUTTON_HEIGTH);
        self.scroller.contentSize = scrollContent;
    }
    int y = 163;
    self.nyttSpillKnapp.frame = CGRectMake(BUTTON_X, y, BUTTON_WIDTH, 65);
    for (int i=0; i<newAntall; i++) {
        UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
        [button setBackgroundImage:[UIImage imageNamed:@"knapp_midt"] forState:UIControlStateNormal];
        [button setBackgroundImage:[UIImage imageNamed:@"knapp_midt"] forState:UIControlStateHighlighted];
        [button setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
        [button.titleLabel setFont:[UIFont systemFontOfSize:15]];
        button.frame = CGRectMake(BUTTON_X, y, BUTTON_WIDTH, BUTTON_HEIGTH);
        button.enabled = YES;
        UISwipeGestureRecognizer *swipe = [[UISwipeGestureRecognizer alloc] initWithTarget:self
                                                                                    action:@selector(deleteButton:)];
        swipe.direction = UISwipeGestureRecognizerDirectionRight;
        [button addGestureRecognizer:swipe];
        button.tag = [[[self.temp.userInfo objectForKey:@"spillider"] objectAtIndex:i] intValue];
        NSString * tittel = [NSString stringWithFormat:@"spill %@",[[self.temp.userInfo objectForKey:@"spillider"] objectAtIndex:i]];
        [button setTitle:tittel forState:UIControlStateNormal];
        UIButton *subButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
        subButton.transform = CGAffineTransformMakeRotation(M_PI_2);
        subButton.tag = i;
        CGRect subframe = CGRectMake(230, 5, subButton.frame.size.width, subButton.frame.size.height);
        subButton.frame = subframe;
        CGRect myframe = self.nyttSpillKnapp.frame;
        myframe.origin.y = myframe.origin.y+BUTTON_HEIGTH;
        self.nyttSpillKnapp.frame = myframe;
        [subButton addTarget:self action:@selector(clickGameButton:) forControlEvents:UIControlEventTouchUpInside];
        [button addSubview:subButton];
        [gameButtons addObject:button];
        [self.scroller addSubview:button];
        y += BUTTON_HEIGTH;
    }
}

To sum up, it only works if I am changing viewcontrollers back and forth causing viewWillAppear to get called. Why is that?

I am sorry for my messy methods.

Thanks

Martol1ni
  • 4,684
  • 2
  • 29
  • 39

1 Answers1

1

If you change the contents of the view outside of the initial view appearing process or layout changes, it's your responsibility to call setNeedsDisplay and inform the run loop that it needs to be redrawn.

The system will ask the view to draw it's contents initially or during layout changes which is why it works as part of the process to first show the view. During that initial process, the viewWill/DidAppear delegates will get called.

From the UIView class reference:

The View Drawing Cycle

View drawing occurs on an as-needed basis. When a view is first shown, or when all or part of it becomes visible due to layout changes, the system asks the view to draw its contents. For views that contain custom content using UIKit or Core Graphics, the system calls the view’s drawRect: method. Your implementation of this method is responsible for drawing the view’s content into the current graphics context, which is set up by the system automatically prior to calling this method. This creates a static visual representation of your view’s content that can then be displayed on the screen.

When the actual content of your view changes, it is your responsibility to notify the system that your view needs to be redrawn. You do this by calling your view’s setNeedsDisplay or setNeedsDisplayInRect: method of the view. These methods let the system know that it should update the view during the next drawing cycle. Because it waits until the next drawing cycle to update the view, you can call these methods on multiple views to update them at the same time.

EDIT:

Also, make sure done getting images is not called on a background thread. You can't edit views on a background thread. If it is you can prepare all the data on a bg thread but then call makeButtons on on the main thread (performSelectorOnMainThread or use blocks.

See GCD, Threads, Program Flow and UI Updating

Community
  • 1
  • 1
bryanmac
  • 38,941
  • 11
  • 91
  • 99
  • I have tried to call [self setNeedsDisplay] at the end of the void makeButtons. Is that the right protocol? If not, what is? – Martol1ni May 05 '12 at 12:42
  • You don't need it at the end of makebuttons (since it's called as part of loading). You can call it after you call makeButtons in doneGettingInformation. – bryanmac May 05 '12 at 12:48
  • I added [self.view setNeedsDisplay] after calling [self makeButtons]. The makeButtons method is being called (NSLoged), but the view still doesn't change... A friend of mine said something about [self performSelector], does that need to apply? I have to wait until makebuttons is finished to tell the view to redraw? Thanks so much. – Martol1ni May 05 '12 at 12:51
  • performSelector is just a way to call a method (selector) dynamically. You do need the setNeedsDisplay but if it's adding the buttons, sounds like there's at least a second issue. Try stepping through it with the debugger to ensure the buttons get added as expected. I have to run out but I'll check back later and look at the code closer. – bryanmac May 05 '12 at 13:19
  • That could definately be the problem. So how would I do, then.. I have to run it from that method. I didn't get it to work with performSelectorOnMainThread from doneGettingInformation. How would you suggest I should do it? I am pretty sure its a background process since its ASIFormDataRequest's startAsynchronously (runs in background). – Martol1ni May 05 '12 at 17:04
  • Understand the sample appication in the link I included. It shows two options in my solution sample including performSelectorOnMainThread and using blocks. I would recommend using the blocks approach. What you would do is call makeButtons by either calling performSelectorOnMainThread or by calling it via blocks on the main queue. Both get that method to execute on the main thread where the message loop and UI redraws are done. In that case you could have makeButtons call setNeedsDisplay. It would be redundant in the viewWillAppear case but not a problem. Hope that helps – bryanmac May 05 '12 at 17:16
  • Thank you very, very much. Nevertheless, I can't get it to work. Is there any way that I can check what thread the function is called from? I have tried performSelectorOnMainThread from the backgroundthread, but I still can't get it to work... – Martol1ni May 05 '12 at 17:37
  • It cant be the makeButtons function that doenst work. I am trying to do (from a background thread) multiple ways of changing a buttons title, and I can't get it to work either way... – Martol1ni May 05 '12 at 17:43
  • Make sure if you do call makeButtons with performSelectorOnMainThread that you move the call to setNeedsDisplay to the makeButtons function. I think you're getting closer – bryanmac May 05 '12 at 18:00
  • Let's hope so. I have found out that ASIRequests (http://allseeing-i.com/ASIHTTPRequest/) is doing all the work on the main thread, so that wasn't the problem.. I have now [self.view setNeedsDisplay]; on the bottom of makeButtons.. It is weird how it doesn't work. – Martol1ni May 05 '12 at 18:19
  • I really, really appreciate the answers. I finally figured it out! Thanks alot, you're a god! – Martol1ni May 05 '12 at 20:14
  • Great. What was the final issue(s)? BTW, if it's on the main thread, your next task may be to figure out how to do it async. network i/o should really be done async without blocking/hanging the UI. Have fun and read the other post showing the long running work in B/G with blocks. – bryanmac May 05 '12 at 21:01
  • Yes, thanks for the directions. All my buttons is in a scroller, so the only thing I was doing wrong was that I didn't use [self.scroller setNeedsDisplay]; (yes, Im embarassed). However, when i move back from the view that is supposed to update and back again, it doesn't work. It only works the first time the view loads. I have probably done something wrong in my ViewDidLoad, since viewDidAppear is able to refresh the view. (setNeedsDisplay is called in makeButtons btw.) – Martol1ni May 05 '12 at 21:34