Speed up ios function - objective-c

I really need help with this!
I'm building a map containing loads of tiles.. like 200x200 small 50px tiles. These tiles resides in an UIScrollView so that I can drag myself around the map! Works really nice right? Well it's super slow since there are too many subviews in the map.
Right, so I load the subviews as I scroll then! Problem is, that takes some time too, makes the scrolling a bit laggy/slow. Can you help me improve this function? I'm out of ideas. Found the Grand Central Dispatch but I'm not sire how to use it.
Here's my code
-(void)loadMapTiles {
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_apply(1, queue,
^(size_t row) {
int radiusX = 11;
int radiusY = 7;
int y = currentCoord.y-radiusY;
for(int x = currentCoord.x-radiusX; x < currentCoord.x+radiusX+1; x++) {
int currTag = mapSize.width*(y-1)+x;
if(![mapScroller viewWithTag:currTag]) {
UIImageView *mapTile = [[UIImageView alloc] initWithFrame:CGRectMake(50*x, 50*y, 50, 50)];
int mapNo = (rand() % 20) + 1;
if(mapNo > 11) {
mapNo = 1;
}
mapTile.image = [UIImage imageNamed:[NSString stringWithFormat:#"desert_map%d_50.gif", mapNo]];
//Tag the mapTile so that we can find it again!
[mapTile setTag:currTag];
[mapContentView addSubview:mapTile];
[mapTile release];
}
if(x == currentCoord.x+radiusX && y < currentCoord.y+radiusY) {
x = currentCoord.x-radiusX-1;
y++;
}
}
//Remove the other tiles outside the radius!
for (UIView *mapTile in [mapContentView subviews]) {
if(mapTile.frame.origin.x / 50 < currentCoord.x-radiusX || mapTile.frame.origin.x / 50 > currentCoord.x+radiusX || mapTile.frame.origin.y / 50 < currentCoord.y-radiusY || mapTile.frame.origin.y / 50 > currentCoord.y+radiusY) {
[mapTile removeFromSuperview];
}
}
});
}
And the function to call the map-building function above:
-(void)scrollViewDidScroll:(UIScrollView *)myScrollView {
CGPoint newCoord = CGPointMake((int)(myScrollView.contentOffset.x + myScrollView.frame.size.width/2) /50, (int)(myScrollView.contentOffset.y + myScrollView.frame.size.height/2) /50);
if(newCoord.x != currentCoord.x || newCoord.y != currentCoord.y) {
currentCoord = newCoord;
[self loadMapTiles];
}
}

You'll want to have a look at CATiledLayer. It lets you do what you want in a nice way regardless of how big your map is. It also supports having different images for different zoom levels.

I'm no obj-c developer but...
At a talk I was at a while ago on mobile web development, they recommend to re-use memory rather than allocate and free it.
Instead of creating a new UIImageView for every tile, could you have enough of them pre-allocated to for the whole screen and just change the images in them as the map is scrolled?

Maybe you should try using CoreGraphics and layers instead of scrollview to draw map and scroll it.

Look at WWDC 2010 video "Designing apps with scrollviews" and the corresponding sample code "PhotoScroller" there you will learn the big trick of "view reuse" in scrollViews.
WWDC 2011 video of similar name is a continuation of the same trick.
"Views that are no longer in the visible area should be reused and repositioned for displaying new stuff"
Adding and removing subviews while scrolling is bound to cause slow downs. Instead you should just reposition the non visible ones and show new stuff.
Other usual causes of scrolling problems are
layer.cornerRadius
layer.shadow (without setting shadowPath)
any opacity on subviews.
Always profile your code on device using core animation.

Related

How to content size of UIScrollview in Autolayout

i want to add 5 views of same size of simulator using UIScrollview. How could i can do? Specially content size of UIScrollview want to set which is approximately equals (320,2840) ?
Looking at your requirement, you need the srollview to scroll vertically as (320,2840)(w,h). While doing this auto layout doesnt create any problem as once your scroll view is set, the 5 views inside id doesnt need to be set via autolayout.
1) First create a Scrollview and set its position on a view. create and IBOutelet for scrollView and then in our viewDidAppear sets its content size to 320,2840 like this
[scrollView setContentSize:CGSizeMake(320, 2840)];
and then create your UIViews in a for loop and add them inside your scrollview with a help a variable say "y" which sets your UIviews y co-ordinate everytime before adding it to your scrollview.
like this
int y = 0;
for (int i = 0; i < 5; i++) {
UIView *insideviews = [[UIView alloc]init];
insideviews.frame = CGRectMake(0, y, 320, scr.frame.size.height);
insideviews.tag = i; // to distinguish them and use later
[scr addSubview:insideviews];
y = y + scr.frame.size.height;
}
thats its, if you still have any problem tell me else you can accept the answer :) Good Luck

How could I create a tiled photo gallery of different widths and heights?

I've been looking to create a photo gallery for an iPad app, where the images are of varying widths and heights. Rather than cropping them to fit in a grid-view, I'd like to create a "mosaic" effect as seen in the Pinterest and Jetsetter app (see links for reference images)
Any idea where I could start to create an effect like this?
For variable height a la Pinterest, the easiest way to go about this is to use a UITableView for each column, as it already provides the mechanism to insert/delete images using UITableViewRowAnimations. It's easier if the height of the image is known, because UITableView asks for it before loading cells. But if it isn't, what you can do is insert each cell sequentially (head or tail) to the shortest UITableView. Load the image, resize if necessary. Disk-cache the heights so you don't have to load the image for every cell. I've successfully implemented it this way.
Addendum: part of the trick in using 3 (or more) UITableViews is to manually sync the scrolling. Otherwise they would scroll on its own:
#pragma mark - UIScrollView stuff
- (void)scrollViewDidScroll:(UIScrollView *)inScrollView {
// Sync the scrolling of all table views
CGPoint offset = inScrollView.contentOffset;
for (NSInteger i = 0; i < self.tableViews.count; i++) {
PLAlbumTableView *tableView = [self.tableViews objectAtIndex:i];
if (tableView != inScrollView)
tableView.contentOffset = offset;
}
}
- (void)scrollViewWillBeginDecelerating:(UIScrollView *)inScrollView {
// Sync the scrolling of all table views
for (NSInteger i = 0; i < self.tableViews.count; i++) {
PLAlbumTableView *tableView = [self.tableViews objectAtIndex:i];
if (tableView != inScrollView)
[tableView setContentOffset:tableView.contentOffset animated:NO];
}
}

Animating frame Images with ease effect on iPad

Let me show you example (360 Deg 3D Object Rotator): Demo: http://activeden.net/item/interactive-renders-360-deg-3d-object-rotator/39718?ref=mixDesign
As you see, there is a camera 3D rotating on mouse event. Actually, it is a collection of images (frames) animating frame by frame depending on mouse event.
I want to implement this animation with objective - c using swipe gesture (or maybe I should use another gesture?). So that I can make rotation by my finger, to the left, to the right (I want animation with smooth ease effect, depending on swipe speed velocity).
Note: I have ready images for each frame.
Sample codes, online tutorials doing this will really help me.
! Should I use some external graphics library, in order to keep performance? I have hundreds of images (PNG), each with size of 300kb
Thank you in advance, I really need your help!
Maybe it will be easier to go with touchesBegan:, touchesMoved:, and touchesEnded: here? This will allow you to react to velocity and direction changes very fast.
Update: example can be found here.
I don't think you should use swipe gesture here. I recommend you LongPressGesture with short minimumPressDuration.
Let me show example code:
longPress = [ [UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(handleLongPressGesture:)]
longPress.delegate = self;
longPress.minimumPressDuration = 0.05;
[viewWithImage addGestureRecognizer:longPress];
float startX;
float displacement = 0;
-(IBAction)handleLongPressGesture:(UILongPressGestureRecognizer *)sender
{
float nowX;
if ( sender.state == UIGestureRecognizerStateBegan )
{
startX = [sender locationInView:viewWithImage].x;
}
if ( sender.state == UIGestureRecognizerStateEnded || sender.state == UIGestureRecognizerStateCancelled)
{
... do something at end ...
}
nowX = [sender locationInView:mainWidgetView].x;
displacement = nowX - startX;
// set right rotation with displacement value
[self rotateImageWith:displacement];
}

Draw waveform in NSView

I need to draw a waveform in an NSView. (I have all the samples in an array). The drawing must be efficient and really fast without clipping, flickering, etc, it must be smooth. The waveform will be "moving" according to the song position and some changes to the samples (DSP processing) will be shown as visual representation onto NSView in realtime.
I'm familiar drawing lines, arcs, etc onto canvas objects and I have developed apps doing such things but not on Mac OS X ...
I want to ask if anyone can guide me where to start drawing! Core Animation, OpenGL, simple override drawing methods, ??, etc. Which would be the best practice - API to use?
I would keep it simple and create an NSView subclass with an audioData property that uses Cocoa drawing. You could call [view setAudioData:waveArray] which would in turn call [self setNeedsDisplay:YES].
In your drawRect: method you could then iterate through the samples and use NSRectFill() accordingly. Here sample's value is between 0 and 1.
- (void)drawRect:(NSRect)dirtyRect {
[[NSColor blueColor]set];
for (id sample in self.waveArray) {
NSRect drawingRect = NSZeroRect;
drawingRect.origin.x = [self bounds].origin.x;
drawingRect.origin.y = [self bounds].origin.y + ([self.waveArray indexOfObject:sample]/([self.waveArray count] - 1.0));
drawingRect.size.width = [self bounds].size.width/[self.waveArray count];
drawingRect.size.height = [self bounds].size.height * [sample value];
NSRectFill(drawingRect);
}
}
This code isn't exact, and you should be sure to make it more efficent by only drawing samples inside dirtyRect.
I would start with a really long and thin image to represent a single bar/column for the waveform.
My plan would be to have a NSTimer that moves all bars of the wave one to the left every 0.01 seconds.
So something like this in the loop.
for (int x; x < [WaveArray count] ; x++)
{
UIImageView * Bar = [WaveArray ObjectAtIndex: x];
[Bar setCenter:CGPointMake(Bar.center.x-1,Bar.center.y)];
}
Now all you have to do is create the objects at the correct hight and add them to the WaveArray and they all will be moved to the left.

Objective-C : Endless UIScrollView without pagingEnabled

In my iPhone app there is a scrollview pagingEnabled=NO which can contain up to 200 subviews (150 x 150) and the challenge is to do lazy loading and simulate endless scrolling (without bouncing) in horizontal directions.
Is there a solution or an alternative for this request?
Lazy loading of a scroll view is demonstrated in one of Apple's sample code projects: PageControl.
To fake endless scrolling what I'd suggest is to make your scroll view very wide to begin with, wider than an average person would scroll in one set of scrolling behaviors. Then in your delegate methods -scrollViewDidEndScrollingAnimation:, -scrollViewDidEndDragging:willDecelerate: and -scrollViewDidEndDecelerating:, one or more of which will be called after the user is done scrolling, reposition your content to exist in the center of the scroll view and update your contentOffset point without animating.
For that to work visually you would need to also disable the horizontal scroller. You'll also need to consider how to determine what view to draw at a particular contentOffset with this method since you won't be able to just divide the contentOffset.x by the scroll view's bounds anymore to find out where you are.
Hello I found the way to do it.
I have a master array with all the subviews (in my case they are images, so I store the names).
The scrollview only has 3 subviews: left, current, right.
Paging is enabled, so the user cant really spin more that one view left/right at any time.
What I do is:
1) track his current position on the master array. If he moves left, subtract one; right add one. Like this:
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
[some code to determine current page, based on contentOffset]
if (page == 0){
NSLog(#"Going left");
if (currentPage > 0){
currentPage--;
} else {
//cycle to last
currentPage = [images count] -1;
}
} else if (page == 2){
NSLog(#"Going right");
if (currentPage < ([images count] -1)){
currentPage++;
} else {
//cycle to first
currentPage = 0;
}
} else{
NSLog(#"Not moving");
}
2) after the user moves, I reload 3 new images, like this:
//updates the 3 views of the scrollview with a new center page.
-(void) updateScrollViewForIndex:(NSInteger)newCenterPage{
//fist clean scroll view
for (UIView *sView in [scroll subviews]){
[sView removeFromSuperview];
}
NSInteger imgCount = [images count];
//set center view
[self loadImageIndex:newCenterPage atPosition:1];
//set left view
if (newCenterPage > 0){
[self loadImageIndex:newCenterPage-1 atPosition:0];
} else {
//its the first image, so the left one is the last one
[self loadImageIndex:imgCount-1 atPosition:0];
}
//set right view
if (newCenterPage < imgCount-1){
[self loadImageIndex:newCenterPage+1 atPosition:2];
} else {
//its the last image, so ther right one is the first one
[self loadImageIndex:0 atPosition:2];
}
}
3) Finally re-center the scroll view to the center view again:
[scroll setContentOffset:CGPointMake(1 * viewWidth, 0)];
Hope this helps, although "the man with the plan" is Mr. Clark, who pointed the way.
Gonso
Matt Gallagher has a blog post which describes a solution to this exact problem. I've used it and it works great.
The UIScrollView and UIPageControl in Cocoa Touch allow for user interfaces with multiple panning pages. The sample project that Apple provides (PageControl) keeps all child views for every page in a lazily loaded array. I'll show you how you can implement this using just two child views, no matter how many virtual pages you wish to represent.
It works by shuffling around the child views and reseting their content when necessary. I used this for displaying flash cards, where there could be anywhere from 3 to 3,000 items. Although it's set up right now for paging, I'm sure you could get it to work with regular scrolling.