Search code examples
iosmultithreadinguiscrollviewuibuttonbackground-process

Adding larg number of Buttons to UIScrollView in background thread - Buttons not visible


the user should be able to select one icon from a large number of different icons. I have created a picker dialog that allows the user to make his selection. The ViewController that is used for this picker only holds one UIScrollView. In viewDidLoad for each icon a button is added to the ScrollView. To select an icon the user just has to click the corresponding button...

This works fine, but the ViewController/picker needs several seconds to be displayed. This is because of the many alloc / add operations within viewDidLoad. Because of this I tried to move these options into a background thread. This workes fine, but the created buttons are not visible any more:

- (void)viewDidLoad {
    [super viewDidLoad];

    self.iconsScrollView.hidden = true;
    [self.activityIndicator startAnimating];

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        iconContainer = [[UIView alloc] init];
        iconContainer.backgroundColor = [UIColor clearColor];

        iconButtons = [[NSMutableDictionary alloc] init];

        CGRect buttonRect = CGRectMake(5, 5, 40, 40);
        selectedButton = nil;

        NSArray *iconInfos = [[StoreController sharedController] allIcons];

        for (IconInfo* iconInfo in iconInfos) {
            NSString *iconName = iconInfo.name;

            UIButton *iconButton = [UIButton buttonWithType:UIButtonTypeCustom];
            iconButton.frame = buttonRect;

            [iconButton addTarget:self action:@selector(iconSelectionClick:) forControlEvents: UIControlEventTouchUpInside];
            [iconButton setImage:[UIImage imageNamed:iconName] forState:UIControlStateNormal];

            [iconContainer addSubview:iconButton];
            [iconButtons setObject:iconButton forKey:iconInfo.guid];

            buttonRect.origin.x += 50;
            if (buttonRect.origin.x > 205) {
                buttonRect.origin.x = 5;
                buttonRect.origin.y += 50;
            }
        }

        iconContainer.frame = CGRectMake(0, 0, self.iconsScrollView.frame.size.width, ceil([iconButtons count] / 5.0) * 50);

        dispatch_async(dispatch_get_main_queue(), ^{
            [self.iconsScrollView addSubview:iconContainer];
            self.iconsScrollView.contentSize = iconContainer.frame.size;

            [self.activityIndicator stopAnimating];
            self.iconsScrollView.hidden = false;

            [self.view setNeedsDisplay];
        });
    });
}

This works (almost) without any problem:

  • Picker ViewController is presented
  • ActivityIndicator is visible while buttons are created
  • Once all buttons are ready ActivityIndicator stops and the ScrollView becomes visible.

Only Problem: The Buttons are not visible. The ScrollView can be used normally (content size correct) and when I touch inside the ScrollView and hit an invisible button the click selector is called. Thus all buttons are there but not visible. Eventually after 10-15 seconds all Buttons become visible at once.

Using setNeedsDisplay or setNeedsLayout for the View, the ScrollView, or the buttons does not change anything.

Any idea what I can do?


Solution

  • You are adding buttons to a subview while you aren't on the main thread.

    Generally, UIKit code should only be run on the main queue.