UIScrollView Lazy Loading Images - ios

My app for the iPhone Contains 170 images. I read them into an array, and in the loop add a picture in the viewfinder, then put the image View as sub view of scroll view.
When running my app uses too much memory 700mb. I tried to reduce the size of the pictures, but it didn't work.
One of my friends said that I should add only images # 1 and # 2. When the user block is scrolled to the picture No. 1, then only show the picture No. 2. Then the picture No. 1 to remove from the image viewer and add the picture No. 3.
He says that in this way I can maintain the normal memory consumption. But I don't understand how to do this?
Could you help with an example? Thanks in advance.
Not using UICollectionView
Here is my code:
- (void)addImageViewToScrollView {
_assetsArray = [[NSMutableArray alloc] init];
for (int j = 0; j < 170; j++) {
[_assetsArray addObject:[UIImage imageNamed:[NSString stringWithFormat:#"%d",j]]];
}
//scrollView add subview ImageView
for (int i = 0; i < [_assetsArray count]; i++) {
CGRect frame;
frame.origin.x = self.scrollView.frame.size.width *i;
frame.origin.y = 0;
frame.size = self.scrollView.frame.size;
_imageView = [[UIImageView alloc]init];
_imageView.image = [_assetsArray objectAtIndex:i];
_imageView.frame = frame;
[self.scrollView addSubview:_imageView];
}
self.scrollView.contentSize = CGSizeMake(self.scrollView.frame.size.width * self.assetsArray.count, self.scrollView.frame.size.height);
}
--

Use a UICollectionViewController to solve this issue.
This way only the cells that are required on screen are loaded and all others images are can be popped from cache when you hit a memory warning.
Collection Views work very similar to table views.
To get the collection view to suit what you're doing you'll need to set the flow layout to horizontal scrolling and set the cell sizes to your view's height and width.

Seems like you are trying to add170images onto UIScrollView using UIImageView. Why not use dedicated UITableView's or UICollectionView's? Object of Reusable Cell is to maintain run-time memory consumption. Let's recap on this,
You have 170 ImageViews (_assetsArray) ; expensive consumption
You have 170 Images
Versus, Using UITableView or UICollectionView
You have 1 ImageView in a Cell with 170 rows
You have DataSource of 170 Images were loaded
Your cell will be re-cycled when it needed to display correspond to DataSource
Also, for supporting the multitude images, there are good libraries to reduce images cache. Try to use SDWebImage or FastImageCache. I personally like FIC but you might need to understand the concept.

Related

Adding Images By incrementing label count

Here,when button taps label count is increments & decrements.While label count increments images will be added by respective count.here my code for incrementing label count
if (counter >= 9 )
return;
[_count setText:[NSString stringWithFormat:#"%d",++counter]];
but i need to display while incrementing the label count then automatically increments images one by one.like this
Can you please help me how can i implement,thank you.
Add a UIScrollView in place of where you want to show images. When click on '+' button, call following method by passing the 'counter' value.
-(void)addImage:(int) count {
UIScrollView * scView = [[UIScrollView alloc] init];
scView.frame = CGRectMake(30, 60, self.view.frame.size.width, 60);
UIButton * btn;
UIImageView * imgV;
float imgWidth = 44;
for(int i=0;i<count;i++) {
imgV = [[UIImageView alloc] init];
imgV.frame = CGRectMake(i*imgWidth, 0, imgWidth, 44);
imgV.image = [UIImage imageNamed:#"gitar"];
[scView addSubview:imgV];
}
[scView setContentSize:CGSizeMake(imgWidth*count, 50)];
[scView setBackgroundColor:[UIColor redColor]];
[self.view addSubview:scView];
}
- (IBAction)descrese:(id)sender {
counter -= 1;
[self addImage:counter];
}
- (IBAction)btnTap:(id)sender {
counter += 1;
[self addImage:counter];
}
This adds as many images as you want in horizontal scroll view.
use a uiscrollview with cells of images, that simple.
Use UIScrollView.
As number of elements is increasing/decreasing:
1) increse/deacrease UIScrollView size respectively
2) add/remove elements
I guess, the screenshot was taken from MMT app. They're using this feature in their app. I believe, they might have developed a separate control to handle this thing, where they can only pass few arguments like image to show (people), count etc.
You can also develop such kind of UI with following way:
Create a custom view, take a UICollectionView inside it, take argument of image to show and number of rows count, fixed the cell size in layouts. Add a method which will reloads UI (inside logic: reloading UICollectionView) to refresh content when user taps on [ + ] or [ - ] button. Why, we're not taking UIScrollView, with it we can't reuse contents. I believe this step will need you to create everything programmatically.
When user taps on [ + ], you should increase width of our custom view, inside
you should override layoutSubview method to update frame of UICollectionView as well. You may require to refresh(reload) cells inside UICollectionView.
Do reverse when user taps [ - ].
You may require to set some logic so user can't add more/less numbers when selection.
This is just an idea, you may require to check each cases which the reference app has having.

Display images in ScrollView

I want to display some images in a scroll view, but I am facing some issues.
Here is what I have:
- (void)viewDidLoad
{
[super viewDidLoad];
myObject = [[NSMutableArray alloc] init];
[myObject addObject:#"tut_5.jpg"];
[myObject addObject:#"tut_2.jpg"];
[myObject addObject:#"tut_3.jpg"];
[myObject addObject:#"tut_4.jpg"];
pageControlBeingUsed = NO;
CGRect screenRect = [[UIScreen mainScreen] bounds];
CGFloat screenWidth = screenRect.size.width;
CGFloat screenHight = screenRect.size.height;
for (int i = 0; i < [myObject count]; i++) {
CGRect frame;
frame.origin.x = self.takeTourScrollView.frame.size.width * i;
frame.origin.y = 0;
frame.size = self.takeTourScrollView.frame.size;
NSString *val = [myObject objectAtIndex:i];
UIImage* theImage = [UIImage imageNamed:val];
UIImageView *img=[[UIImageView alloc] initWithFrame:CGRectMake(screenWidth*i,0,185 ,284)];
img.image = theImage;
[self.takeTourScrollView addSubview:img];
}
The first first image seems to be ok.
Then when I swipe left I am getting a blank screen
I swipe left again and then I see my second picture. And it goes like that for all the 4 images.
Any ideas what am I doing wrong ?
There are system components that will give you what you want with much less work and a more refined user experience. Using a UICollectionView is one possibility, as mentioned by Vive in her answer. My suggestion would be a UIPageViewController. There is a sample app called PhotoScroller from Apple that shows how to implement a UI very much like what you describe. Do a search in the Xcode docs for "PhotoScroller" to find it.
I'd advise to use UITableView/UICollectionView for such behaviour. You can set the image in each cell and they'll be reused - it will be much better in terms of memory management.
The tableView will by the way place all elements in proper places (you just need to define the height of the cell or the layout).
It will also properly resize on events (eg phone rotation).
Also, you should try to avoid hardcoding the sizes:
CGRectMake(screenWidth*i,0,185 ,284)
You will fail in case of smaller / bigger devices. It'll be a great pain to maintain the code after some time.
-- edit --
After #Duncan C posted his answer, I've noticed you're looking for a paging system. You can go with building your own solution either on UIPageViewController or on UICollectionView. However you can also use third party libraries, I really enjoy this one: https://www.cocoacontrols.com/controls/bwwalkthrough. It has a support of different animations and does a ton of stuff for you :).
you already set the x position of uiscrollview. do not need to set the x postion of UIImageView.Because you used imageview as a subview of uiimagescrollview.
change this line to
UIImageView *img=[[UIImageView alloc] initWithFrame:CGRectMake(screenWidth*i,0,185 ,284)];
with
UIImageView *img=[[UIImageView alloc] initWithFrame:CGRectMake(0,0,185 ,284)];

iOS Image Viewer: Set Zoomed Image Back when Scrolling

I have a gallery view that I use to, surprise, view images. :) I wanted it to be very similar to the standard iOS photo app with the same basic functionality: scrolling through images that are zoomable. To do this, I use a regular scroll view which I then populate with other scroll views that contain the images. The basic idea is something like this:
And, I do the populating the following way:
for (int i = 0; i < [imageGalleryArray count]; i++)
{
UIScrollView *imageContainer = [[UIScrollView alloc] initWithFrame:CGRectMake((windowSize.width * i), 0.0f, windowSize.width, windowSize.height)];
imageContainer.contentSize = CGSizeMake(windowSize.width, windowSize.height);
imageContainer.showsHorizontalScrollIndicator = NO;
imageContainer.showsVerticalScrollIndicator = NO;
imageContainer.bouncesZoom = YES;
imageContainer.delegate = self;
imageContainer.minimumZoomScale = 1.0f;
imageContainer.maximumZoomScale = 3.0f;
UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, windowSize.width, windowSize.height)];
imageView.image = [[imageGalleryArray objectAtIndex:i] objectForKey:#"image"];
imageView.contentMode = UIViewContentModeScaleAspectFit;
imageView.tag = IMAGEZOOMVIEW;
imageView.opaque = YES;
[imageContainer addSubview:imageView];
[galleryScrollView addSubview:imageContainer];
// Add it to array for proper removal later
[contentScrollViewSubviewList addObject:imageContainer];
}
Quite simple and it works fine. The only problem arises when I am zoomed into an image and want to scroll to another. The expected behavior in this case would be to set the zoom level of all images back to their original size. I do this in the scrollViewDidEndDecelerating delegate method like so:
if (scrollView.tag == GALLERYSCROLLVIEW)
{
for (int i = 0; i < [contentScrollViewSubviewList count]; i++)
{
[[contentScrollViewSubviewList objectAtIndex:i] setZoomScale:1.0f animated:YES];
}
}
Unfortunately, it does not work (or rather not as intended). I did some poking around and found out that the image does indeed animate back to its original size when it is the active slide. To elaborate: if I zoom into an image and scroll to another, the image stays zoomed in. When I scroll back to it, that is when it zooms back to its original size. I, however, need the image to scale back as soon as I move away from it ().
just like in the iOS image viewer).
Frankly, I do not understand the whole zoom mechanic enough to find out how to do this properly, and the UIScrollView documentation is of no help either. Could someone show me how to solve my problem?

Indefinite width horizontal Scroll view to display images

new to iOS development. I'll explain the core purpose, the app should display an array of images on a horizontally scrolling view, these images will be loaded from the app documents itself. But the number of images stored in the app may vary from user to user. So,
I want to create a horizontally scrolling view (UIScrollView/UITableView) which can hold UIImage. I want to change the width of the view as more images are read, but increasing the width of a UIScrollView wouldn't add a UIImage placeholder for the new images.
OR
Should I try with UITableView, any help would be much appreciated !
Use UICollectionView instead of UIScrollView.
Check this SO link.
Here using collectView is easy as it has delegate method same as UITableView.
The best way is to start a UIScrollView, add it to your ViewController and write something like this:
self.scrollView.contentSize = CGSizeMake(self.pictures.count * 170 + 10, self.scrollView.frame.size.height);
Where the 170 for me is the width of the image + 10 (like to have some space between them). The +10 afterwards is 10px after my images too (end of the UIScrollView).
Later, to put on those images you can do
for (int x = 0; x < self.pictures.count; x++) {
UIImageView *imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"myImage"]];
imageV.frame = CGRectMake(10 + x * 170, 10, 160, 120);
[self.scrollView addSubview:imageV];
}
Replace the imageNamed with your own image name or something like that...
This is only for about max 20 pictures, if you have a lot of pictures then I would suggest using a collection. multiple threads have started about those...
You can use icarousel in ios https://github.com/nicklockwood/iCarousel
There are many types to choose from.
Hope that might help you !!!

scrollview is showing only the last added subview

I am getting strange behavior with UIScrollView subviews, the idea is to create programmatically an instance of UIView with a customized nib file which is a form in my case, fill that form with data from a model class, and add it as subview for my UIScrollView. The problem is when I deal with more than one subview, the UIScrollView only keep the latest subview, so if I created three subviews, the scrollview will show only the third (the latest) subview. Although the page control is set to the coreect number of subviews (three).
The project is too long, so I will try to explain brievely my issue with the relevant code:
- (void)viewDidLoad {
NSArray *sorted = [appArray sortedArrayUsingDescriptors:[NSArray arrayWithObject:sortDescriptor]];//this array contains the data from model, I debugged that to make sure I got the exact data, no more no less :)
//Loop all NSManaged objects, for example let's say I have 3 model objects, don't worry about how I get data etc, because I debugged all and maked sure all data objects number are exact, etc.
for (App_Table *_appTable in sorted) {
//This looped 3 times as expected, I debugged that also and maked sure on each iteration I got the data I expected to have
//App_Table is a subclass of NSManagedObject, it's my model class and get its data from coredata file
[self addMoreView];//Call this method will create a new subview and add it as subview to the UIScrollView, it will also update the page control, update the content size property, etc.
AppTableView *_appTableView = (AppTableView *) [[self.scrollView subviews] lastObject];//Remember addMoreView method create a new instance of AppTableView and add it as subview for the UIScrollView property, then I get that subview to fill it with data here
_appTableView.txtADDRESS.text = _appTable.address;//Fill in the form, no need to write all the form fields code because it's the same way.
// Scroll To First Page...
self.pageControl.currentPage = 0;
[self.scrollView scrollRectToVisible:CGRectMake(0, 0, viewWidth, viewHeight) animated:YES];
self.scrollView.contentSize = CGSizeMake(viewWidth*noOfItems, viewHeight);//Set the content size to the sum of subviews width, I also debugged that to check it's correct
self.scrollView.delegate = self;
[super viewDidLoad];
}
Ok, so when I have three subviews, the scrollview will load with the width of three subviews as calculated with the line above:
self.scrollView.contentSize = CGSizeMake(viewWidth*noOfItems, viewHeight);//Set the content size to the sum of subviews width, I also debugged that to check it's correct
And I can scroll till 3 moves (which is the number of subviews) and the UIPageControl is also set to three dots, but only ONE subview is visible, only the latest one I can see, the two other subviews disappeared, the scroll view calculated content size for them but they are not visible. Any thoughts ? Thanx.
EDIT:
It's worth to note that the first time I edit the view, all goes fine, when I deal with 3 subviews, they are all visible, but when I go to another view and get back to this view, only the last subview is visible.
Also, I am working on an iPad project for that with a split view.
EDIT:
This is the code of the method which draw new subview for the UIScrollView
-(IBAction) addMoreView
{
NSArray *arr = [[NSBundle mainBundle] loadNibNamed:#"AppTableView" owner:self options:nil];
AppTableView *aView = [arr objectAtIndex:0];
[aView setFrame:CGRectMake(startX, 0, aView.bounds.size.width, aView.bounds.size.height)];
[self.scrollView addSubview:aView];
self.scrollView.contentSize = CGSizeMake(self.scrollView.contentSize.width+aView.frame.size.width
, self.scrollView.contentSize.height);
startX = startX + aView.frame.size.width;//Update the X position, first 0, then 600, 1200, and so on.
[self.scrollView scrollRectToVisible:aView.frame animated:YES];
NSLog(#"%f",self.scrollView.contentSize.width);
NSLog(#"%f",self.scrollView.contentSize.height);
}
Suppose I have 3 subviews, the method above will be called 3 times since it's put inside the for loop. So here is the result of NSLogs for the above method:
NSLog(#"%f",self.scrollView.contentSize.width);
NSLog(#"%f",self.scrollView.contentSize.height);
First iteration:
600.000000
0.000000
Second iteration:
1200.000000
0.000000
Third iteration:
1800.000000
0.000000
EDIT:
The command suggested by rob mayoff allows me to see why this happen:
| | | | <AppTableView: 0x1eef4700; frame = (0 18; 600 430); autoresize = LM+RM+TM+BM; tag = 990; layer = <CALayer: 0x1eef4790>>
| | | | <AppTableView: 0x1ee3f380; frame = (0 18; 600 430); autoresize = LM+RM+TM+BM; tag = 991; layer = <CALayer: 0x1ee3f410>>
| | | | <AppTableView: 0x1ee56910; frame = (0 18; 600 430); autoresize = LM+RM+TM+BM; tag = 992; layer = <CALayer: 0x1ee3f410>>
All Three subviews are drawn in the same frame, so they are above each other, but when I debug that with breakpoints in runtime, the x is changing, first time 0, then 600 then 1200 which make me think it's drawing correctly. How to fix that especially that the x value is being incremented correctly, so what's the problem and why they still drawing on the same x coordinate?
First things first, you should place the [super viewDidLoad] call at the top of your - (void)viewDidLoad method, not at the bottom.
Given that it's not clear enough what you are actually seeing and what you expect to see, providing a minimum working example of your problem as a downloadable project would help us to help you. If you just try to reproduce this in a separate view controller that does not interact with managed objects, but uses some statically provided data, others will be able to reproduce it themselves and debug it. Or you might just as well figure it out yourself in the process.
I am getting back a bit late, but finally I figure out the fix for my problem so thought it's good to share it to save someone else's time.
For my case, the scrollview is a custom view in a nib file. So by activating its Autosizing masks left and top, the bug was fixed.
This block of code in -addMoreView is the problem:
AppTableView *aView = [arr objectAtIndex:0];
[aView setFrame:CGRectMake(startX, 0, aView.bounds.size.width, aView.bounds.size.height)];
[self.scrollView addSubview:aView];
If startX never changes, you're adding all of these views at the same spot.
Surely your not setting the content size correctly for 3 items:
self.scrollView.contentSize.width+aView.frame.size.width
should be
self.scrollView.contentSize.width+ ( aView.frame.size.width * NUMBER_OF_SUBVIEWS)
I presume when you scroll horizontally on the scrollview, the content size only allows enough room for one subview correct ?
used a static variable which is reinitialized to 0 on load, then it gets incremented by the width of the subview to start drawing on new X position.
Try to set the autoresizingMask of aView to UIViewAutoresizingNone inside the addMoreView method.
I created a project that uses your code in the sister post, and its working just fine. I added several tests on the autoresizing masks as you can see below. I suggest you compare it to what you are doing. Weird things happen when the masks on EITHER the subviews or the scrollView are not set to being tied to the top/left.
My modified code:
NSArray *colors = #[ [UIColor redColor], [UIColor greenColor], [UIColor blueColor] ];
UIViewAutoresizing mask;
mask = [self.scrollView autoresizingMask];
//assert(!self.scrollView.autoresizesSubviews); // has no affect here
if((mask & (UIViewAutoresizingFlexibleRightMargin|UIViewAutoresizingFlexibleBottomMargin)) != mask) NSLog(#"scrollView MASK WRONG");
for (int i=0; i<3; ++i) {
NSArray *arr = [[NSBundle mainBundle] loadNibNamed:#"AppTableView" owner:self options:nil];
NSLog(#"scrollView frame: %#", NSStringFromCGRect(self.scrollView.frame));
if((mask & (UIViewAutoresizingFlexibleRightMargin|UIViewAutoresizingFlexibleBottomMargin)) != mask) NSLog(#"aView MASK WRONG");
AppTableView *aView = [arr objectAtIndex:0];
assert([aView isKindOfClass:[AppTableView class]]);
NSLog(#"orig aView frame: %#", NSStringFromCGRect(aView.frame));
UIViewAutoresizing mask = [aView autoresizingMask];
if((mask & (UIViewAutoresizingFlexibleRightMargin|UIViewAutoresizingFlexibleBottomMargin)) != mask) NSLog(#"aView MASK WRONG");
aView.frame = CGRectMake(startX, 0, aView.bounds.size.width, aView.bounds.size.height);
NSLog(#"changed aView frame: %#", NSStringFromCGRect(aView.frame));
aView.backgroundColor = colors[i];
[self.scrollView addSubview:aView];
self.scrollView.contentSize = CGSizeMake(self.scrollView.contentSize.width+aView.frame.size.width
,self.scrollView.contentSize.height);
startX = startX + aView.frame.size.width;
//AppTableView *_appTableView = (AppTableView *) [[self.scrollView subviews] lastObject];
//_appTableView.txtADDRESS.text = _appTable.address;//Fill in the form, no need to write all the form fields code because it's the same way.
}
NSLog(#"scrollView frame: %#", NSStringFromCGRect(self.scrollView.frame));
NSLog(#"scrollView contentOffset: %#", NSStringFromCGPoint(self.scrollView.contentOffset));

Resources