I'm having a lot of trouble dynamically adding subviews to a UIScrollView. The scroll view works fine with content created in a NIB but since the subviews to be displayed depend on data in my application (a mixture of images, labels, radio buttons, etc.) I need to be able to create and display them dynamically.
According to everything I've read it seems pretty straightforward on various sites and in the Apple documentation. In the view controller's viewDidLoad, I've added the following code,
UILabel *testLabel = [[UILabel alloc] init];
[testLabel setFrame:CGRectMake(50, 50, 100, 40)];
[testLabel setText:#"My Test label"];
[scrollView addSubview:testLabel];
[testLabel release];
The label will not appear in the scroll view at all but if I add the testLabel to self.view, then it appears (but not in the scrolling content obviously). I even tried adding the code to viewDidAppear in case I misunderstood the order of events with no luck.
When I checked the debugger, I noticed that the address of the scroll view is 0x0 which I assume means its null for some reason which would explain why its not working. I was under the assumption that if I connected this scrollView pointer to the actual scroll view in IB, it would be automatically assigned the correct address. Is this incorrect? If this is the case, how do I go about getting the address of the view?
-- UPDATE --
Thanks for all the feedback. I checked everything as everybody suggested and it was certainly all correct. I didn't need to set the size of the content as I had some other dummy labels (for testing that the scrolling was working) in the NIB. But I'll remember that for later on :-)
Interestingly, though, after checking the code again and not making any changes, I ran it again and it just worked!! Not sure why but I'll post the reason if I ever figure it out...
When you use scrollView you need to set the content size by doing:
scrollView.contentSize = CGSizeMake(#width,#height);
In this case the size should be bigger the 50,50 if you wanna see the label
Hope it helped
As described in your question You are working with a scrollview which you have added in the XIB.
When you declare an outlet as IBOultlet UIScrollView* scrlvDynamicContent;
and connect the same to your scrollview in interface builder,ideally you will get the allocated scrollview after viewdidLoad method is called.
so doing something as
-(void)viewDidLoad{
[super viewDidLoad];
[self createDynamicView];
}
where you can generate the dynamic view as follows
-(void)createDynamicView{
CGFloat yOffset = 0;
for (int i=0;i<5;i++) {
yOffset += 5;
UILabel* lblHeaderTitle = [[UILabel alloc] initWithFrame:CGRectMake(8, yOffset, 310, 21)];
[lblHeaderTitle setTextAlignment:UITextAlignmentLeft];
[lblHeaderTitle setFont:[UIFont fontWithName:#"Helvetica-Bold" size:16.0f]];
[lblHeaderTitle setText:[currentDict valueForKey:#"TITLE"]];
[lblHeaderTitle setTextColor:[UIColor blackColor]];
[scrlvDynamicContent addSubview:lblHeaderTitle];
[lblHeaderTitle release];
//INCREMNET in yOffset
yOffset += 25;
[scrlvDynamicContent setContentSize:CGSizeMake(320, yOffset)];
}
Just make sure that scrlvDynamicContent is connected properly to its outlet set to its file owner
[scrollView setFrame:CGRectMake(0,0,320,460)];
[scrollView setContentSize:CGSizeMake(100, 40)];
[self.view addSubview:scrollView];
UILabel *testLabel = [[UILabel alloc] init];
[testLabel setFrame:CGRectMake(50, 50, 100, 40)];
[testLabel setText:#"My Test label"];
[scrollView addSubview:testLabel];
[testLabel release];
Related
In my iPhone app, I am dynamically adding a UIScrollView and adding n number of UIImages and UIButtons into the scrollview. Here, the images are loaded from different urls and the button titles are coming from SQlite database. Everything is fine. But when I scroll the scrollview, now I am getting memory warning Level=1 and after some time it is Level=2 and crashes the app. I am using ARC. How can I fix this problem?
Code
- (void)setUpViewLayout{
int newContentSize = [appDelegate.itemArray count] * 125;
menuItemIdArray = [[NSMutableArray alloc]init];
mainView = [[UIScrollView alloc]initWithFrame:CGRectMake(0, 100, 480, 220)];
mainView.contentSize = CGSizeMake(newContentSize, 220);
mainView.tag = 100;
mainView.delegate = self;
mainView.userInteractionEnabled = YES;
mainView.backgroundColor = [UIColor clearColor];
int xPosition = 20;
for (tagVal = 0; tagVal < [appDelegate.itemArray count]; tagVal++) {
[self createImage:xPosition];
[self createButton];
xPosition = xPosition + 120;
}
[self.view addSubview:mainView];
}
- (void)createImage:(int)xPosition{
DataBaseClass *itemObj = [appDelegate.itemArray objectAtIndex:tagVal];
NSString *url = [NSString stringWithFormat:#"%#",itemObj.notAvialableIcon];
imgView = [[UIImageView alloc]initWithFrame:CGRectMake(xPosition+8, 48, 110, 123)];
imgView.userInteractionEnabled = YES;
imgView.tag = tagVal;
[imgView setImageWithURL:[NSURL URLWithString:url] placeholderImage:[UIImage imageNamed:#"item01.png"]];
[mainView addSubview:imgView];
}
- (void)createButton{
DataBaseClass *itemObj = [appDelegate.itemArray objectAtIndex:tagVal];
button = [UIButton buttonWithType:UIButtonTypeCustom];
button.frame = CGRectMake(5, 90, 100, 26);
button.tag = tagVal;
button.userInteractionEnabled = YES;
button.tag = tagVal;
button.titleLabel.textColor = [UIColor blueColor];
button.titleLabel.font = [UIFont systemFontOfSize:9.0];
NSString *name = [NSString stringWithFormat:#"%#",itemObj.itemStatus];
itmName = [NSString stringWithFormat:#"%#",itemObj.itemName];
NSString *date = [self changeDateFormat:itemObj.itemReleaseDate];
[button setTitle:date forState:UIControlStateNormal];
button.userInteractionEnabled = NO;
button setBackgroundImage:[UIImage imageNamed:#"not_available_bttn_bck_img"] forState:UIControlStateNormal];
[imgView addSubview:button];
}
Don't add all subviews to the scroll view at one time. That's too expensive.
When scroll view did scroll, get the visible rect of the scroll view, and add your image and button just fit in than range or more than a little of that rect.
When the visible subview is not visible, remove from the super view.
I'm opening a new response for this one. It's the simple solution we all missed.
From the code you posted, this is just a table view on its side. So, you don't have to build your own tiled scroll view.
Here's a bit of code to get you started. When you set up the table view, rotate it by 90 degrees, set the row height and eliminate the separator lines:
tableView.transform = CGAffineTransformMakeRotation(0.5 * M_PI);
tableView.rowHeight = 120.0;
tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
You'll have to set the table view's frame so that it's in the correct position after rotation. Essentially, it's the same as your current scroll view's frame, or as that frame on its side.
Here are a couple of the table view's data source methods:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [appDelegate.itemArray count];
}
The table cells can be very simple custom table cells that just have a single image view and a button on that image view. You might also rotate the image view so that the image is displayed correctly, not on its side. Or, you could rotate all your images in a photo editor or image editor before loading them.
That's pretty much it. The table view, as always, will take care of recycling your cells and optimizing your memory usage.
You need to identify which part of your code causes the leak. You can do this several ways.
One way is to use the built in analyzer in xcode. It analyzes your code and detect (some) potential memory leaks.
The instruments tool is also a good tool to find these leaks. Start it using the allocation/leak component. Go to your scrollview, and do a sample after scrolling the view. Your leak should show up. Now you can track down the leak and have instruments locate the correct place in your code directly.
The third option is to go through your code and try and figure out what is happening yourself. Understanding memory managment is a vital part of programming for ios devices.
What about posting the code your are using in your scrollview here, so we can take a look?
Abhishek is absolutely correct that all subviews must be released after being added to a superview. That will cause a leak. Specifically, once the scroll view comes off screen and is released, its subviews will not be released as they should. They will still have a retain count of 1, from when they were alloc'ed.
However, as long as the scroll view is still on screen, there is no leak. A superview retains all its subviews (i.e. increases their retain count by 1.) If a subview was alloc'ed but not released, it's retain count is 2. If it was alloc'ed and released its retain count is 1. Either way, as long as the scroll view exists, its subviews are still, correctly, retained.
If you are receiving memory warnings while the scroll view is still up, the problem may not be the leak, just over-usage of memory. If you keep adding images to a large scroll view, you will certainly run into memory overage problems.
To fill a large scroll view with images, but avoid memory overages, you might take a look at the ScrollViewSuite demo's third example, on tiling. That should work well for you since your images and buttons are the same size, and can act as the tiles.
The idea is to make a sort of table view out of the scroll view that now recycles image tiles instead of cells. The scroll view is subclassed and a set of reusable tiles is kept as one of its instance variables. The key to the implementation is, in layoutSubviews, to remove from superview the tiles that have moved out of the visible area, then recycle tiles for newly visible content and add them as subview. In this way, only visible tiles are loaded into memory. And, it recycles tiles just like a table view recycles cells.
From the size of your scroll view, it may be that you have no other option than to tile and recycle. Nonetheless, it's a good option.
Update: Wubao Li essentially summarizes what needs to be done. The ScrollViewSuite demo shows you how.
//you had allocated the things but did not release it ... it was the reason of leak
- (void)setUpViewLayout{
int newContentSize = [appDelegate.itemArray count] * 125;
// menuItemIdArray = [[NSMutableArray alloc]init]; why you are allocating this array
UIScrollView *mainView = [[UIScrollView alloc]initWithFrame:CGRectMake(0, 100, 480, 220)];
mainView.contentSize = CGSizeMake(newContentSize, 220);
mainView.tag = 100;
mainView.delegate = self;
mainView.userInteractionEnabled = YES;
mainView.backgroundColor = [UIColor clearColor];
int xPosition = 20;
for (tagVal = 0; tagVal < [appDelegate.itemArray count]; tagVal++) {
[self createImage:xPosition];
[self createButton];
xPosition = xPosition + 120;
}
[self.view addSubview:mainView];
[mainView relese];//release scroll view here
}
- (void)createImage:(int)xPosition{
DataBaseClass *itemObj = [appDelegate.itemArray objectAtIndex:tagVal];
NSString *url = [NSString stringWithFormat:#"%#",itemObj.notAvialableIcon];
imgView = [[UIImageView alloc]initWithFrame:CGRectMake(xPosition+8, 48, 110, 123)];
imgView.userInteractionEnabled = YES;
imgView.tag = tagVal;
[imgView setImageWithURL:[NSURL URLWithString:url] placeholderImage:[UIImage imageNamed:#"item01.png"]];
[mainView addSubview:imgView];
[imgView release]; //release imageview here
}
- (void)createButton{
DataBaseClass *itemObj = [appDelegate.itemArray objectAtIndex:tagVal];
button = [UIButton buttonWithType:UIButtonTypeCustom];
button.frame = CGRectMake(5, 90, 100, 26);
button.tag = tagVal;
button.userInteractionEnabled = YES;
button.tag = tagVal;
button.titleLabel.textColor = [UIColor blueColor];
button.titleLabel.font = [UIFont systemFontOfSize:9.0];
NSString *name = [NSString stringWithFormat:#"%#",itemObj.itemStatus];
itmName = [NSString stringWithFormat:#"%#",itemObj.itemName];
NSString *date = [self changeDateFormat:itemObj.itemReleaseDate];
[button setTitle:date forState:UIControlStateNormal];
button.userInteractionEnabled = NO;
button setBackgroundImage:[UIImage imageNamed:#"not_available_bttn_bck_img"] forState:UIControlStateNormal];
[imgView addSubview:button];
}
may it help you
There are 3 suggestions for you here:
Try loading images in background thread
Check this response Does iOS 5 have garbage collection?
Use leak, instrument to find out where your application is leaking, and then manage that part for the best
This is the bug of Apple.
UIScrollView will LEAK even these codes:
UIScrollView *s = [[UIScrollView alloc] initWithFrame:self.view.bounds];
s.contentSize = CGSizeMake(320, 800);
[self.view addSubview:s];
[s release];
I have a 320x460 view with a number of buttons, depending on the button pressed, a 280x280 view pops up over the 320x460 view (similar to the behavior of the UIAlertView) using code like this:
UIView *overlayView = [[UIView alloc] initWithFrame:CGRectMake(20, 200, 280, 280)];
overlayView.backgroundColor = [UIColor whiteColor];
[overlayView autorelease];
[overlayView addSubview:label]; // label declared elsewhere
[overlayView addSubview:backgroundImage]; // backgroundImage declared elsewhere
//... Add a bunch of other controls
[label release];
[backgroundImage release];
//... Release a bunch of other controls
[self.view addSubview:overlayView];
Everything works fine displaying the overlayView and all its controls.
The question I have is, how do I get rid of the overlayView once it's displayed? I want to make it not only not visible but to remove it completely, since the user will be popping up the overlayView repeatedly during use.
You need access to overlayView to remove it, I'd suggest adding this to the create side:
overlayView.tag = 5; // Or some other non-zero number
Then later you can use it like this:
-(void)removeOverlayView
{
UIView *overlayView = [self.view viewWithTag:5];
[overlayView removeFromSuperview];
}
I understand that there is a tableHeaderView property, but when ever I add my view to that, it is not hidden above the scroll area.
What I would like to have is, my custom view shown when you pull down the tableview and hold and you see my UIView brought into view. This is done on many apps to put a logo or such slightly hidden until a user pulls down on the tableview (Twitter/Facebook when you pulldown).
I am currently using the following and it is not putting it out of the view:
UILabel *l = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 300, 20)];
l.text = #"Hidden Text";
l.textColor = [UIColor redColor];
self.tableView.tableHeaderView = l;
[l release];
Since UITableView is actually a UIScrollView with some extra functionality, you can use contentInset to obtain the effect you want. The trick is to use a negative value for the top inset. This will normally hide your header view, but it will still be viewable when the table bounces.
So, after you add the label to the header view, just set the contentInset like this:
UILabel *l = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 300, 20)];
l.text = #"Hidden Text";
l.textColor = [UIColor redColor];
self.tableView.tableHeaderView = l;
//add this
[self.tableView setContentInset:UIEdgeInsetsMake(-l.bounds.size.height, 0.0f, 0.0f, 0.0f)];
[l release];
The best solution here is to add your view to the header, as you had mentioned you tried, and then in your controller's viewDidLoad actually scroll the tableview downward programmatically until the header view you wanted hidden is hidden. This can be done a number of different ways. The easiest is probably:
[self.tableView setContentOffset: CGPointMake(0, myHeaderHeight)];
Simply have a 0-height header view, and then have a subview of that be positioned with a negative y, and so that the bottom edge of the subview is the top of the view.
UIWindow* window = [[UIApplication sharedApplication].delegate.window;
[window addSubview: your-overlayview];
I've seen lots of sources saying it is possible to include a UIScrollView with UIPageControl inside a UITableViewCell to be able to scroll horizontally (through a list of selectable images), but can't find any raw examples that does what I want. I've gotten my scroll view "working", but I am unsure how to go forward - hopefully someone can send me some guidance.
Within my cellForRowAtIndexPath, I create a cell, and add both a scrollView and pageControl as subviews.
UITableViewCell *cell = [self.challengeListView dequeueReusableCellWithIdentifier:SubmitChallengeCellIdentifier];
if(cell == nil){
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:SubmitChallengeCellIdentifier] autorelease];
cell.frame = CGRectMake(0, 0, 1000, 50);
}
scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, tv.frame.size.width, 50)];
[scrollView setContentSize:CGSizeMake(1000, 50)];
[[cell contentView] addSubview:scrollView];
pageControl = [[UIPageControl alloc] initWithFrame:CGRectMake(0, 50, tv.frame.size.width, 50)];
[pageControl setNumberOfPages:4];
[[cell contentView] addSubview:pageControl];
I've attached a screenshot of what's being displayed
the bottom portion of the main view is my UITableView that contains the scrollView/pageControl (and it will scroll horizontally, as I can see the scrollerIndicator showing this), and I've got its method's set to the following:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
-(NSInteger) tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return 1;
}
My Scroll view will indicate a "horizontalScroller" and I am able to scroll back and forth, but obviously there's no content there. How do I go about populating the tableView/scrollView with say, a list of "clickable" images? Any direction would be greatly appreciated - preferably not a "hey this guy's done it somewhat similar, check out this link" but maybe more an explanation of how this functionality should be implemented correctly (ESPECIALLY in regards to iOS 3.0+ - it is my understanding Apple has made our lives easier in implementing this)
I've solved my own problem; maybe the reason no once answered me is because its a minor implementation once you understand each view's purpose.
From within cellForRowAtIndexPath:
I created a standard UITableViewCell, however I altered the frame of the cell to my own custom frame of 1000 width by 50 height (catered to my needs for project).
I then created a UIScrollView, set it to the following (keep in mind I have my tableView defined in IB, so I'm mapping some of my height/widths to those values):
scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, tv.frame.size.width, 78)];
I then create the desired image view (I realize I will next create a loop that does many images and lays them out across the scroll view):
UIImage *image = [UIImage imageNamed:#"dummy.png"];
UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
imageView.frame = CGRectMake(0, 0, 80, 78);
[scrollView addSubview: imageView];
Here's the part I was missing. After adding the scrollView to the cell contents, you need to use the UIPageControl (which didn't seem obvious to me for this implementation at first) to setup the actual "visual horizonal scrolling" affect:
[[cell contentView] addSubview:scrollView];
pageControl = [[UIPageControl alloc] initWithFrame:CGRectMake(0, 50, tv.frame.size.width, 50)];
[pageControl setNumberOfPages:4];
[[cell contentView] addSubview:pageControl];
Hope that helps someone's search - I spent quite some time on Google looking for the example I just explained and didn't have much luck other than the general overview of how it would work.
I am wanting to show a simple loading dialog when certain things are happening in my app. I figured I would just create a new view, add a label to that, and then set that view to a subView of my current view.
When doing this, I don't see anything!
Here is how I am writing my method:
- (void)showLoading {
UIView *loading = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 200, 200)];
loading.backgroundColor = [UIColor blackColor];
UILabel *txt = [[UILabel alloc] initWithFrame:CGRectMake(198, 9, 94, 27)];
txt.text = #"Loading...";
txt.textColor = [UIColor whiteColor];
[loading addSubview:txt];
[super.view addSubview:loading];
[super.view bringSubviewToFront:loading];
[loading release];
[txt release];
}
Am I doing this completely wrong?
EDIT:
I added it to the viewDidLoad method, and it works how I want:
loading = [[UIView alloc] initWithFrame:CGRectMake(100, 100, 200, 200)];
loading.backgroundColor = [UIColor blackColor];
UILabel *txt = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 94, 27)];
txt.text = #"Loading...";
txt.textColor = [UIColor whiteColor];
[loading addSubview:txt];
[txt release];
[self.view addSubview:loading];
[self.view bringSubviewToFront:loading];
But when loading it from a method, it seems to lag, and not show up for a bit.
Although this doesn't directly answer your question, I'd recommend grabbing MBProgressHUD from GitHub and using that in place of a static label. Looks better, less code for you to directly maintain, etc. You can find it at http://github.com/matej/MBProgressHUD
The way I use it is by creating a subclass of UITableViewController and defining a handful of methods to show and hide the HUD view. From there, I call each relevant method when I'm loading or done loading.
Specifically, I have four methods: -hudView, -showLoadingUI, -showLoadingUIWithText:, and -hideLoadingUI.
-hudView creates a new MBProgressHUD object if one doesn't already exist, and adds it to the current view ([self.view addSubview:hudView]).
-showLoadingUI calls -showLoadingUIWithText: with a default title, -showLoadingUIWithText: just unhides the MBProgressHUD and sets a label value for it (self.hudView.labelText = #"foo";).
-hideLoadingUI hides the hudView ([self.hudView hide:YES]).
First, I don't think UIView has method called init. You may just call the super of it. The appropriate method you should call is - (id)initWithFrame:(CGRect)aRect . The frame is the position, the size of the View you want to display. More here
Another thing is why you call [super.view addSubView:], I think it should be self.view, isn't it?