Update UIview with new objects - ios

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

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

Related

Add custom Button with AsyncDisplayKit

I am developing an IOS application. I use Facebook AsyncDisplayKit library. I want to a button in ASNodeCell Bu I got "Variable 'node' is uninitialized when captured by block. How can I add UIButton or UIWebView control in ASNodeCell. Please help me
dispatch_queue_t _backgroundContentFetchingQueue;
_backgroundContentFetchingQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
dispatch_async(_backgroundContentFetchingQueue, ^{
ASDisplayNode *node = [[ASDisplayNode alloc] initWithViewBlock:^UIView *{
UIButton *button = [UIButton buttonWithType:UIButtonTypeSystem];
[button sizeToFit];
node.frame = button.frame;
return button;
}];
// Use `node` as you normally would...
node.backgroundColor = [UIColor redColor];
[self.view addSubview:node.view];
});
Note that in your case there's no need to use UIButton, you can use ASTextNode as a button since it inherits from ASControlNode (same goes for ASImageNode). This is described at the bottom of the first page of the guide: http://asyncdisplaykit.org/guide/. That will also allow you to do the text sizing on the background thread instead of the main thread (the block you provide in your example is executed on the main queue).
For completeness I'll also comment on the code you provided.
You're trying to set the frame of the node in the block when you're creating it, so you're trying to set the frame on it during its initialization. That causes your problem. I don't think you actually need to set the frame on the node when you're using initWithViewBlock: because internally ASDisplayNode uses the block to directly create its _view property which is added to the view hierarchy in the end.
I also noticed you're calling addSubview: from the background queue, you should always dispatch back to the main queue before you call that method. AsyncDisplayKit also adds addSubNode: to UIView for convenience.
I've changed you're code to reflect the changes though I recommend you use ASTextNode here.
dispatch_queue_t _backgroundContentFetchingQueue;
_backgroundContentFetchingQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
dispatch_async(_backgroundContentFetchingQueue, ^{
ASDisplayNode *node = [[ASDisplayNode alloc] initWithViewBlock:^UIView *{
UIButton *button = [UIButton buttonWithType:UIButtonTypeSystem];
[button sizeToFit];
//node.frame = button.frame; <-- this caused the problem
return button;
}];
// Use `node` as you normally would...
node.backgroundColor = [UIColor redColor];
// dispatch to main queue to add to view
dispatch_async(dispatch_get_main_queue(),
[self.view addSubview:node.view];
// or use [self.view addSubnode:node];
);
});

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?
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.
UIKit can only be updated from the main thread
dispatch_async(dispatch_get_main_queue(), ^(void){
//Run UI Updates
});

UIScrollView not scrolling when adding UIButtons as subviews

I'm trying to build a simple UIScrollView with paging to horizontally scroll between 3 images.
The tricky part is that I would like that each image would be clickable and catch the click event.
My technique is to create 3 UIButtons that each consists UIImage. give each button a tag and set an action.
Problem: I can catch the click event - BUT it's not scrollable!
Here is my code:
- (void) viewDidAppear:(BOOL)animated {
_imageArray = [[NSArray alloc] initWithObjects:#"content_01.png", #"content_02.png", #"content_03.png", nil];
for (int i = 0; i < [_imageArray count]; i++) {
//We'll create an imageView object in every 'page' of our scrollView.
CGRect frame;
frame.origin.x = _contentScrollView.frame.size.width * i;
frame.origin.y = 0;
frame.size = _contentScrollView.frame.size;
//
//get the image to use, however you want
UIImage* image = [UIImage imageNamed:[_imageArray objectAtIndex:i]];
UIButton* button = [[UIButton alloc] initWithFrame:frame];
//set the button states you want the image to show up for
[button setImage:image forState:UIControlStateNormal];
[button setImage:image forState:UIControlStateHighlighted];
//create the touch event target, i am calling the 'productImagePressed' method
[button addTarget:self action:#selector(imagePressed:)
forControlEvents:UIControlEventTouchUpInside];
//i set the tag to the image #, i was looking though an array of them
button.tag = i;
[_contentScrollView addSubview:button];
}
//Set the content size of our scrollview according to the total width of our imageView objects.
_contentScrollView.contentSize = CGSizeMake(_contentScrollView.frame.size.width * [_imageArray count], _contentScrollView.frame.size.height);
_contentScrollView.backgroundColor = [ENGAppDelegate backgroundColor];
_contentScrollView.delegate = self;
}
Well, since UIButton is an UIControl subclass, it "eats up" the touches of your scroll view:
[UIScrollView touchesShouldCancelInContentView:] The default returned value is YES if view is not a UIControl object; otherwise, it returns NO.
(from https://developer.apple.com/library/ios/documentation/uikit/reference/UIScrollView_Class/Reference/UIScrollView.html#//apple_ref/occ/instm/UIScrollView/touchesShouldCancelInContentView:)
You could influence this by subclassing UIScrollView and overwriting touchesShouldCancelInContentView: (and/or touchesShouldBegin:withEvent:inContentView:). However, for your use case I'd not use buttons in the first place. Why not just add a tap gesture recognizer to the scrollview and use the touch point to determine which image has been tapped? That's much easier and should work without any issues.
This completely solved this issue for me:
scrollview.panGestureRecognizer.delaysTouchesBegan = YES;
Credit goes to https://stackoverflow.com/users/904365/kjuly for providing the right answer in this topic: ScrollView/TableView with UIControl/UIButton subviews not scrollable under iOS 8

When adding several subviews ( > 4), UIButton stops responding to touches

I am creating a modal view in an App, which contains several subviews. Each one of those subviews has a button and an action related to that button. I use a loop to insert each subview in place. The problem is that the first 4 subviews are ok, from the 5th to the last there are no responses.
Here it is the simplified code related to the problem:
Simplified SubviewView.m
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// More info button
CGRect buttonFrame = CGRectMake(infoMarginLeft + infoWidthWithInfo, infoHeightControl, 25, 25);
UIButton * button = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
[button setFrame:buttonFrame];
[button addTarget:self action:#selector(buttonTapped:) forControlEvents:UIControlEventTouchUpInside];
[self addSubview:button];
}
return self;
}
-(void)buttonTapped:(id)sender
{
NSLog(#"Button tapped!";
}
Simplified view.m
- (void)drawRect:(CGRect)rect
{
for (int i = 0; i < 12; i++) {
// Color info container
SubviewView * miniView = [[SubviewView alloc] initWithFrame:CGRectMake(0, 20 * i, 15, 15)];
miniColorView.backgroundColor = [UIColor clearColor];
// Adding subview through array to keep track of objects
[self addSubview:miniColorView];
}
}
Thanks in advance, I have absolutely no ideia of what's going on :p
--
EDIT
Just found out what it was!
The parent view had as height the screen height. The thing is, some of the subviews were beyond the screen height limit. All I had to do was increase the parent view height =)
I would check to make sure you are not adding your view miniView over top of your button.
Also this should be moved outside of drawRect, just put this in your init method.
Edit:
Set your miniView's background color to another besides clear, and see if you still see your button. If not, then you covered up your button, and it won't receive touch events.

The current way to release memory from a UIScrollview with many images

I am generating a simple scrollView with some images attached to buttons.
This works fine apart from the fact that this scroll view is taking rather much memory.
Since this scrollView is just a sub Menu allowing the user to pick an image and soon after I do not need it, I would like to free this heavy block from memory.
Can you kindly help me out understanding this issue, and free this huge memory block when not needed
int flipFlop = 1;
masksAvailable = 18;
float topMaskXX = 85.0;
float topMaskYY = 96.0;
UIButton *button;
for (int buttonsLoop = 1;buttonsLoop < masksAvailable+1;buttonsLoop++){
button = [UIButton buttonWithType:UIButtonTypeCustom];
NSString *tempname = [NSString stringWithFormat:#"mask_frame%i.png",buttonsLoop];
// This fellow there is the memory eating monster
[button setBackgroundImage:[UIImage imageNamed:tempname] forState:UIControlStateNormal];
tempname = nil;
button.tag = buttonsLoop;
[button addTarget:self action:#selector(handleMaskKeys:) forControlEvents:UIControlEventTouchUpInside];
UIImageView *frameForSubImages = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"image_frame.png"]];
frameForSubImages.frame = CGRectMake(0.0, 0.0, 320.0/2.9, 480.0/2.9);
frameForSubImages.center = CGPointMake(topMaskXX,topMaskYY);
[scrollView addSubview:frameForSubImages];
button.frame = CGRectMake(0.0, 0.0, 320.0/3.4, 480.0/3.4);
button.center = CGPointMake(topMaskXX,topMaskYY);
if (flipFlop == 1){
topMaskXX += 150;
} else {
topMaskYY += 185.0;
topMaskXX = 85.0;
}
flipFlop = flipFlop * -1;
[scrollView addSubview:button];
}
First of all, I'd like to suggest that you do a "Clean All" and a "Build and Analyze" on your project. It is very good at pointing out problems with retain/release.
Secondly, any class that retains objects should define a "dealloc" that releases those objects to make sure they get deleted when the object is released.
-(void) dealloc {
// release all retained objects here.
[super dealloc];
}
Thirdly, in your example above, it also looks like frameForSubImages might have an extra retain on it, since you've allocated it (+1 reference) and assigned it to a view (+1 reference) without ever calling release (which would be -1 reference and leave you with a refcount of 1).
Finally, I would also recommend reading the Memory Management Programming Guide for iOS.

Resources