How to load more data in UITableView after scrolling - objective-c

I have Thrift server, I can get information and load them in my UITableView, at the first I load 10 rows, and after scrolling to the end each time I want to load 10 more rows(with information) but it's never work for me,
would you please help me in this implementation,
Thanks in advance!
Here is my numberOfRowsInSection method
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return _notes.count;
}
my method scrollViewDidScroll
-(void)scrollViewDidScroll: (UIScrollView*)scrollView
{
float scrollViewHeight = scrollView.frame.size.height;
float scrollContentSizeHeight = scrollView.contentSize.height;
float scrollOffset = scrollView.contentOffset.y;
if (scrollOffset == 1)
{
[self.tableView setContentOffset:CGPointMake(0, 44)];
}
else if (scrollOffset + scrollViewHeight == scrollContentSizeHeight)
{
// I don't know what should I put here
NSLog(#"%# we are at the end", _notes); //==> _notes is null
//we are at the end, it's in the log each time that scroll, is at the end
}
Here is how I connect to the server
- (void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:YES];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,
(unsigned long)NULL), ^(void) {
NSURL *url = [NSURL URLWithString:BASE_URL];
THTTPClient *transport = [[THTTPClient alloc] initWithURL:url];
TBinaryProtocol *protocol = [[TBinaryProtocol alloc]
initWithTransport:transport
strictRead:YES
strictWrite:YES];
server = [[thrift_Client alloc] initWithProtocol:protocol];
_notes = [server get_notes:10 offset:0 sort_by:0 search_for:result];
__weak NotesTable *weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf.notesTable reloadData];
});
});
[self.view setNeedsDisplay];
}

Use SVPullToRefresh, it offers nice encapsulation. Add the library and register your UITableView with
[tableView addInfiniteScrollingWithActionHandler:^{
// prepend data to dataSource, insert cells at top of table view
// call [tableView.pullToRefreshView stopAnimating] when done
}];

Hi use this it works perfectly for me try this...
- (void)scrollViewDidScroll:(UIScrollView *)aScrollView
{
CGPoint offset = aScrollView.contentOffset;
CGRect bounds = aScrollView.bounds;
CGSize size = aScrollView.contentSize;
UIEdgeInsets inset = aScrollView.contentInset;
float y = offset.y + bounds.size.height - inset.bottom;
float h = size.height;
float reload_distance = 10;
if(y > h + reload_distance)
{
//Put your load more data method here...
}
}
}

You should put your request. The one that is executing on viewdidappear.. remove from viewdidappear put it inside a method. Then call this method from viewdidappear and from this place you put the log and instead of your implementation of didscroll this is one is better
- (void)scrollViewDidScroll: (UIScrollView *)scroll {
NSInteger currentOffset = scroll.contentOffset.y;
NSInteger maximumOffset = scroll.contentSize.height - scroll.frame.size.height;
// Change 10.0 to adjust the distance from bottom
if (maximumOffset - currentOffset <= 10.0) {
[self methodThatAddsDataAndReloadsTableView];
}
}

Related

MKPolygonRenderer - tough memory issues

i am currently facing a hard memory issue while using the new iOS7 MKPolygonRenderer class.. I pinpointed the source of the issue to a single line of code:
[renderer invalidatePath];
it seems like the core framework is not releasing the memory here, so that subsequent calls to this function result in an application crash due to memory exceptions.
basically what i want to do is to let the user alter a single polygon overlay on the map.
#interface MapViewController () <MKMapViewDelegate>
{
MKPolygonRenderer* renderer;
}
#end
#implementation MapViewController
- (id) init
{
if ((self=[super init]) != nil)
{
MKMapView* viewMap = [[MKMapView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height)];
viewMap.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
viewMap.delegate = self;
[self.view addSubview:viewMap];
viewMap.region = MKCoordinateRegionMake(CLLocationCoordinate2DMake(49.391759,8.675766), MKCoordinateSpanMake(0.35, 0.35));
// a simple polygon overlay with 4 points
CLLocationCoordinate2D overlayCoords[4] = { {49.421247,8.607101}, {49.418121,8.745117}, {49.321094,8.734818}, {49.320199,8.613968}/*, {49.370199,8.583968} */};
MKPolygon* overlay = [MKPolygon polygonWithCoordinates:overlayCoords count:4];
[viewMap addOverlay:overlay];
// the gesture recognizer which is used to alter the polygon
UILongPressGestureRecognizer* longPressRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(longPress_recognized:)];
longPressRecognizer.minimumPressDuration = 0.1f;
[viewMap addGestureRecognizer:longPressRecognizer];
}
return self;
}
- (MKOverlayRenderer*)mapView:(MKMapView*)mapView rendererForOverlay:(id<MKOverlay>)overlay
{
// reuse the renderer if already existent
if (self->renderer == nil)
{
NSLog(#" renderer is nil ==> creating");
renderer = [[MKPolygonRenderer alloc] initWithPolygon:overlay];
renderer.fillColor = [[UIColor darkGrayColor] colorWithAlphaComponent:0.4];
renderer.strokeColor = [UIColor greenColor];
renderer.lineWidth = 1;
}
else
NSLog(#" renderer is not nil ==> reusing");
return self->renderer;
}
- (void) longPress_recognized:(UILongPressGestureRecognizer*)sender
{
if (sender.state == UIGestureRecognizerStateBegan)
{
// begin drag
// check if and if yes, which polygon point is touched
// set indexOfSelectedPoint
}
else if (sender.state == UIGestureRecognizerStateChanged)
{
// drag move
if (indexOfSelectedMapPoint == -1)
return;
// get the coordinate of the user touch location
CLLocationCoordinate2D c = [self->viewMap convertPoint:[sender locationInView:self->viewMap] toCoordinateFromView:self->viewMap];
// update the coordinate of touched polygon point
self->renderer.polygon.points[indexOfSelectedMapPoint] = MKMapPointForCoordinate(c);
// this line causes the trouble
[renderer invalidatePath];
}
else if (sender.state == UIGestureRecognizerStateEnded || sender.state == UIGestureRecognizerStateCancelled)
{
// drag end
// reset states
}
}
my test project is using ARC. The profiler is not complaining about memory leaks.
is anybody having similar issues?
am i doing something completely wrong here?
is there a better way of doing this?
thanks for the help in advance

animate UIView in a loop

I am creating an app that will show how a bubble sort works. The one problem I am having is the animation. It seems to be skipping and the animation is firing all at once. Within my if statement I want to swap the two images and animate the swap. Then I want a delay so once they swap it will go back through the loop and swap the next two images. Below is my code. I have tried many things like sleep() but it did not seems to work. The images are four boxes and I'm swapping them by left to right, smallest to largest.
for (int i = 0; i < 3; i++)
{
UIImageView *temp1 = [imagesArray objectAtIndex:i];
UIImageView *temp2 = [imagesArray objectAtIndex:i+1];
temp1Width = temp1.frame.size.width;
temp2Width = temp2.frame.size.width;
if (temp1Width > temp2Width)
{
CGPoint tempPoint = temp1.center;
[UIView animateWithDuration:1.0
delay: 1.0
options: nil
animations:^{
temp1.center = temp2.center;
}
completion:^(BOOL finished){
[UIView animateWithDuration:1.0
delay: 0.0
options: nil
animations:^{
temp2.center = tempPoint;
}
completion:^(BOOL finished){
}];
}];
[imagesArray exchangeObjectAtIndex:i+1 withObjectAtIndex:i];
}
}
Any Help would be appreciated.
If you apply an animation to a view that's already being animated, the new animation replaces the existing animation. It doesn't just get tacked on to the end. That's why your version doesn't work the way you want.
This happens even if you set a delay on the new animation. The existing animation will be removed, and the new animation will start after the specified delay. This prevents k20's simple answer from working. (Too bad, because that would be a really easy solution.)
So, a very simple alternative is to defer adding each additional animation at all. This is pretty easy with the dispatch_after function. First, let's factor out the code that swaps the views' Y origins into a helper function:
static void swapViewYOrigins(UIView *a, UIView *b) {
CGRect aFrame = a.frame;
CGRect bFrame = b.frame;
CGFloat t = aFrame.origin.y;
aFrame.origin.y = bFrame.origin.y;
bFrame.origin.y = t;
a.frame = aFrame;
b.frame = bFrame;
}
Now we can write the sort function, making it use dispatch_after to postpone each successive animation:
- (IBAction)sortButtonWasTapped {
NSTimeInterval duration = 1;
dispatch_time_t time = DISPATCH_TIME_NOW;
for (int i = imagesArray.count - 1; i > 0; --i) {
for (int j = 0; j < i; ++j) {
UIView *a = imagesArray[j];
UIView *b = imagesArray[j+1];
if (a.frame.size.width > b.frame.size.width) {
imagesArray[j] = b;
imagesArray[j+1] = a;
dispatch_after(time, dispatch_get_main_queue(), ^{
[UIView animateWithDuration:duration delay:0 options:UIViewAnimationOptionBeginFromCurrentState animations:^{
swapViewYOrigins(a, b);
} completion:nil];
});
time = dispatch_time(time, duration * NSEC_PER_SEC);
}
}
}
}
So this is an ok solution, but it does have one shortcoming. It's not easy to add a button that cancels the animations, because there's no API that removes a pending dispatch_after block from the queue.
If you want to support cancelation, it's better to explicitly manage the queue of pending operations. We can do that by adding an instance variable to hold the queue:
#implementation ViewController {
IBOutletCollection(UIView) NSMutableArray *imagesArray;
NSMutableArray *pendingSwaps;
}
Then, we modify sortButtonWasTapped to add a block to the queue instead of calling dispatch_after, and we make it start running the queue after it finishes sorting:
- (IBAction)sortButtonWasTapped {
pendingSwaps = [NSMutableArray array];
for (int i = imagesArray.count - 1; i > 0; --i) {
for (int j = 0; j < i; ++j) {
UIView *a = imagesArray[j];
UIView *b = imagesArray[j+1];
if (a.frame.size.width > b.frame.size.width) {
imagesArray[j] = b;
imagesArray[j+1] = a;
[pendingSwaps addObject:^{
swapViewYOrigins(a, b);
}];
}
}
}
[self runPendingSwaps];
}
We're using the same swapViewYOrigins function as before. We run the pending swaps like this:
- (void)runPendingSwaps {
if (pendingSwaps.count == 0)
return;
void (^swapBlock)(void) = pendingSwaps[0];
[pendingSwaps removeObjectAtIndex:0];
[UIView animateWithDuration:1 animations:swapBlock completion:^(BOOL finished) {
[self runPendingSwaps];
}];
}
So, we just stop if the queue is empty. Otherwise, we take the first block off the queue, and we use it as the animation block of a UIView animation. We give the animation a completion block that calls runPendingSwaps again, which starts the next pending animation (if there is one) after the current one finishes.
To cancel, we can just set pendingSwaps to nil:
- (IBAction)cancelButtonWasTapped {
pendingSwaps = nil;
}
Note that if the user taps Cancel, the views will not necessarily be in the same order on the screen as they are in imagesArray, because imagesArray is fully sorted when sortButtonWasTapped returns. We can fix that by sorting imagesArray by Y origins (using the built-in sort) before starting the bubble sort:
- (IBAction)sortButtonWasTapped {
[self sortImagesArrayByY];
// etc. same as previous version
}
- (void)sortImagesArrayByY {
[imagesArray sortUsingComparator:^NSComparisonResult(id obj1, id obj2) {
CGFloat d = [obj1 frame].origin.y - [obj2 frame].origin.y;
return
d < 0 ? NSOrderedAscending
: d > 0 ? NSOrderedDescending
: NSOrderedSame;
}];
}
With this in place, you can click the Sort button, then click Cancel before the views have been fully sorted on screen, then click Sort again and it will resume (by performing bubble sort again).
Try doing it implicitly with a timer instead of using a loop and a delay. For example, you could use a scheduled NSTimer whose duration is equal to or greater than the animation time to fire this method:
- (void)performNextAnimation:(NSTimer *)timer
{
BOOL didSwap = NO;
for(NSInteger i = 0; i < [self.imageViews count] - 1; ++i)
{
UIImageView *image1 = [self.imageViews objectAtIndex:i];
UIImageView *image2 = [self.imageViews objectAtIndex:(i + 1)];
if(image1.frame.size.width <= image2.frame.size.width)
continue;
CGPoint center1 = image1.center;
CGPoint center2 = image2.center;
[UIView animateWithDuration:AnimationDuration
delay:0
options:UIViewAnimationOptionCurveEaseInOut
animations:^
{
image1.center = center2;
image2.center = center1;
}
completion:nil];
[self.imageViews exchangeObjectAtIndex:(i + 1) withObjectAtIndex:i];
didSwap = YES;
break;
}
if(!didSwap)
{
[self.animationTimer invalidate];
self.animationTimer = nil;
NSLog(#"Done!");
}
}
This simply finds the first two views that are out of order and swaps them, moving them to one another's center point simultaneously. You can, of course, choose any type of animation to perform for each out-of-order pair.
For completeness, here's how I set up my timer and views in the first place:
self.animationTimer = [NSTimer scheduledTimerWithTimeInterval:AnimationDuration
target:self
selector:#selector(performNextAnimation:)
userInfo:nil
repeats:YES];
CGFloat viewCenterX = self.view.center.x;
self.imageViews = [NSMutableArray array];
const CGFloat imageHeight = 35;
const CGFloat imageMargin = 10;
for(size_t i = 0; i < 10; ++i)
{
CGFloat imageWidth = arc4random_uniform(250) + 30;
CGRect imageFrame = CGRectMake(viewCenterX - imageWidth * 0.5, i * (imageHeight + imageMargin) + imageMargin,
imageWidth, imageHeight);
UIImageView *imageView = [[UIImageView alloc] initWithFrame:imageFrame];
imageView.backgroundColor = [UIColor redColor];
[self.view addSubview:imageView];
[self.imageViews addObject:imageView];
}
Even if you call the animateWithDuration:delay: different times, they are being called at almost the same time (the "for" time). As all of them have the same delay, you don't see the animation.
An easy fix would be using a dynamic delay:
[UIView animateWithDuration:AnimationDuration
delay:i
...

NSMatrix with multiple toggle buttons?

I am trying to create an NSMatrix of NSButtonCells where between zero and four buttons can be selected (toggled on). I have tried the following (test) code, but am not sure how I can provide the functionality I require. Perhaps it's not possible with NSMatrix and I need to look at an alternative control, or create my own?
#interface MatrixView : NSView
{
NSScrollView *_scrollView;
NSMatrix *_matrixView;
}
#end
#implementation MatrixView
- (id)initWithFrame:(NSRect)frameRect
{
NSLog(#"initWithFrame. frameRect=%#", NSStringFromRect(frameRect));
self = [super initWithFrame:frameRect];
if (self != nil)
{
_scrollView = [[NSScrollView alloc] initWithFrame:NSMakeRect(0, 0, frameRect.size.width, frameRect.size.height)];
[_scrollView setBorderType:NSNoBorder];
[_scrollView setHasVerticalScroller:YES];
[_scrollView setHasHorizontalScroller:NO];
[_scrollView setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable];
NSSize contentSize = [_scrollView contentSize];
contentSize.height = 300;
// Make it 3 x however-many-buttons-will-fit-the-height
CGFloat gap = 8.0;
CGFloat width = (contentSize.width / 3.0) - (gap * 2.0);
NSUInteger rows = (contentSize.height / (width + gap));
NSLog(#"width=%f, rows=%lu", width, rows);
NSButtonCell *prototype = [[NSButtonCell alloc] init];
[prototype setTitle:#"Hello"];
[prototype setButtonType:NSToggleButton];
[prototype setShowsStateBy:NSChangeGrayCellMask];
_matrixView = [[NSMatrix alloc] initWithFrame:NSMakeRect(0, 0, contentSize.width, contentSize.height)
mode:NSListModeMatrix
prototype:prototype
numberOfRows:rows
numberOfColumns:3];
[_matrixView setCellSize:NSMakeSize(width, width)];
[_matrixView setIntercellSpacing:NSMakeSize(gap, gap)];
[_matrixView setAllowsEmptySelection:YES];
[_matrixView sizeToCells];
[_scrollView setDocumentView:_matrixView];
[self addSubview:_scrollView];
[self setAutoresizesSubviews:YES];
[prototype release];
}
return self;
}
...
I got this to work with the following subclass of NSMatrix. I added one property, onCount, to keep track of how many buttons were in the on state:
#implementation RDMatrix
#synthesize onCount;
-(id) initWithParentView:(NSView *) cv {
NSButtonCell *theCell = [[NSButtonCell alloc ]init];
theCell.bezelStyle = NSSmallSquareBezelStyle;
theCell.buttonType = NSPushOnPushOffButton;
theCell.title = #"";
if (self = [super initWithFrame:NSMakeRect(200,150,1,1) mode:2 prototype:theCell numberOfRows:4 numberOfColumns:4]){
[self setSelectionByRect:FALSE];
[self setCellSize:NSMakeSize(40,40)];
[self sizeToCells];
self.target = self;
self.action = #selector(buttonClick:);
self.drawsBackground = FALSE;
self.autoresizingMask = 8;
self.allowsEmptySelection = TRUE;
self.mode = NSHighlightModeMatrix;
self.onCount = 0;
[cv addSubview:self];
return self;
}
return nil;
}
-(IBAction)buttonClick:(NSMatrix *)sender {
NSUInteger onOrOff =[sender.selectedCells.lastObject state];
if (onOrOff) {
self.onCount += 1;
}else{
self.onCount -= 1;
}
NSLog(#"%ld",self.onCount);
if (self.onCount == 5) {
[sender.selectedCells.lastObject setState:0];
self.onCount -= 1;
}
}
When you try to select the 5th button it will flash on, but then go off. This could be a problem depending on how you are using the state of these buttons. I just logged them with this method:
-(IBAction)checkMatrix:(id)sender {
NSIndexSet *indxs = [self.mat.cells indexesOfObjectsPassingTest:^BOOL(NSButtonCell *cell, NSUInteger idx, BOOL *stop) {
return cell.state == NSOnState;
}];
NSLog(#"%#",indxs);
}
After Edit: I didn't like the way my first method flashed the button on briefly before turning it off again when you try to click the 5th button. I found what I think is a better solution that involves overriding mouseDown in the matrix subclass (if you want to try this, you should delete the setAction and setTarget statements and delete the buttonClick method):
-(void)mouseDown:(NSEvent *) event {
NSPoint matPoint = [self convertPoint:event.locationInWindow fromView:nil];
NSInteger row;
NSInteger column;
[self getRow:&row column:&column forPoint:matPoint];
NSButtonCell *cell = [self cellAtRow:row column:column];
if (self.onCount < 4 && cell.state == NSOffState) {
cell.state = NSOnState;
self.onCount += 1;
}else if (cell.state == NSOnState) {
cell.state = NSOffState;
self.onCount -= 1;
}
}

NSMutableArray in Class Method

I've created a class method that returns a random position as an NSValue object.
I want to store the returned positions in a mutable array so the next time the class method gets called it checks the positions in the mutable array and if it finds one that is too close to the one stored it generates a new one.
What I can't seem to figure out is how to allocate and initialize my mutable array to use with my class method. I've tried allocating it in the class method. but that won't work because it will get initialized on each call therefor the previously stored values are... erased?
then I want to use the generated position to place a number of circles on the view (without them overlapping). right now the randomly generated positions work, but they overlap because of the mutable array not being implemented correctly in my randomposition class.
I'm still a beginner in objective c...and programming in general...
#implementation randomPosition
NSMutableArray *positionsArrayPlayer1;
+(NSValue *) givePosition:(int)player forRadius: (CGFloat) radius
{
if (player == 1)
{
//set range for arc4random
int fromNumberY = 550;
int toNumberY = 950;
//set range for arc4random
int fromNumberX = 0;
int toNumberX = 700;
BOOL far_enough_away = NO;
CGPoint newpoint;
while(!far_enough_away)
{
newpoint = CGPointMake((arc4random()%(toNumberX-fromNumberX+1))+fromNumberX,
(arc4random()%(toNumberY-fromNumberY+1))+fromNumberY);
far_enough_away = YES;
for(NSValue *existing in positionsArrayPlayer1)
{
NSLog(#"test");
CGPoint pointb = [existing CGPointValue];
CGFloat deltay = pointb.y-newpoint.y;
CGFloat deltax = pointb.x-newpoint.x;
CGFloat distance = sqrt(pow(deltax,2) + pow(deltay,2));
//fail if closer than desired radius
if(distance < radius )
{
far_enough_away = NO;
NSLog(#"test");
break;
}
[positionsArrayPlayer1 addObject:[NSValue valueWithCGPoint:newpoint]];
}
NSLog(#"%#",positionsArrayPlayer1);
}
return [NSValue valueWithCGPoint:newpoint];
} else if (player == 2 ){
//more stuff to come here....
} else {
NSLog(#"player invalid");
}
return nil;
}
here's the method I use in my viewcontroller. I also store the circle object that is placed on the view in a separate array for other manipulations.
- (void) positionCirclesPlayer1
{
radius = 70;
CGRect position;
for (int i = 0; i < [colors count];i++)
{
CGRect positionCircleInCenter = CGRectMake(self.view.frame.size.width/2-35, self.view.frame.size.height/2-35, radius, radius);
Circle *myCircle = [[Circle alloc] initWithFrame:positionCircleInCenter radius:radius color:[colors objectAtIndex:i]];
myCircle.label.text = [NSString stringWithFormat:#"%i", i];
myCircle.alpha = 0;
myCircle.userInteractionEnabled = NO;
theNewPointPlayer1 = [randomPosition givePosition:1 forRadius:radius];
position = CGRectMake(theNewPointPlayer1.CGPointValue.x, theNewPointPlayer1.CGPointValue.y, 70, 70);
[UIView animateWithDuration:0.4 delay:0 options:UIViewAnimationCurveEaseIn animations:^{
myCircle.frame = position; myCircle.alpha = 1;} completion:nil];
[playerOneCircles addObject:myCircle];
[self.view addSubview:myCircle];
}
}
Before learning Obj-C, you should probably learn something about C :)
+(NSValue*)givePosition:(int)player forRadius:(CGFloat)radius {
//this is executed when the method is called for the first time
static NSMutableArray *positionsArrayPlayer1 = nil;
if (positionsArrayPlayer1 == nil) { //checking if this is the first time execution
positionsArrayPlayer1 = [[NSMutableArray alloc] init];
}
}
or
//global variable accessible only from this file
static NSMutableArray *positionsArrayPlayer1;
#implementation randomPosition
//this method is called when the class is loaded.
+(void)initialize {
positionsArrayPlayer1 = [[NSMutableArray alloc] init];
}
+(NSValue*)givePosition:(int)player forRadius:(CGFloat)radius {
//do something with the array
}
#end
Note that implementing this functionality using class methods is probably not a very good architecture.

iOS: Multiple intersecting views

I am making a day view calendar just like the native iPhone calendar. I am trying to position the tiles the same as in the native calendar, side by side, if they are the same size and same time.
However, I can only figure out how to do it to 2 tiles and not multiple tiles. In the attached image I have 4 tiles. One that expands slightly into the other 3. I then have the first tile on the far left and the second tile just after the first one. Now I need to figure out how to add the additional tiles?
How would I do this for more than 2 tiles?
About the image: If you can't see it the 3rd tile is ontop of the 2nd tile (you can see it is a bit darker since they are on top of each other.
- (void)layoutSubviews
{
// Set the main
for (UIView *view in self.subviews) {
APCalendarDayTile *tile = (APCalendarDayTile *)view;
CGFloat startPos = [APCalendarCurrentDayView yAxisForTime:[APCalendarCurrentDayView minutesToTime:tile.appointment.startDate]];
CGFloat endPos = [APCalendarCurrentDayView yAxisForTime:[APCalendarCurrentDayView minutesToTime:tile.appointment.endDate]];
tile.frame = CGRectMake(kLeftSideBuffer, startPos, (self.bounds.size.width - kLeftSideBuffer) , endPos - startPos);
tile.backgroundColor = [UIColor colorWithHexString:tile.appointment.appointmentColor];
}
for (UIView *view in self.subviews) {
APCalendarDayTile *tile = (APCalendarDayTile *)view;
if ([self viewIntersectsWithAnotherView:tile]) {
}
}
}
- (BOOL)viewIntersectsWithAnotherView:(UIView*)selectedView{
NSArray *subViewsInView=[self subviews];// I assume self is a subclass
// of UIViewController but the view can be
//any UIView that'd act as a container
//for all other views.
for (UIView *theView in subViewsInView){
if (![selectedView isEqual:theView]) {
if(CGRectIntersectsRect(selectedView.frame, theView.frame)) {
if ((selectedView.frame.origin.y == theView.frame.origin.y) && (selectedView.frame.size.height == theView.frame.size.height)) {
if (theView.frame.size.width == self.bounds.size.width - kLeftSideBuffer) {
theView.frame = CGRectMake(theView.frame.origin.x, selectedView.frame.origin.y, theView.frame.size.width / 2, selectedView.frame.size.height);
}
selectedView.frame = CGRectMake(theView.frame.origin.x + theView.frame.size.width, selectedView.frame.origin.y, theView.frame.size.width, selectedView.frame.size.height);
return YES;
}
}
}
}
return NO;
}
It appears that your test
if ((selectedView.frame.origin.y == theView.frame.origin.y) && (selectedView.frame.size.height == theView.frame.size.height))
Is only applied to views of equal y origin and height. I would solve this problem using the following pseudo code:
initialize an empty arranged subviews array
initialize a nil previous subview
for every subview
if the subview intersects with the previous subview
ensure the subview and the previous subview are added to the arranged subviews array
else if the arranged subviews array is not empty
arrange the subviews in the array across the width of their superview
empty the arranged subview array
Ok,
I sorta took SaltyMule's approach however, his pseudo code didn't make sense in the if / else.
- (void)layoutSubviews
{
// Set the main
for (UIView *view in self.subviews) {
APCalendarDayTile *tile = (APCalendarDayTile *)view;
CGFloat startPos = [APCalendarCurrentDayView yAxisForTime:[APCalendarCurrentDayView minutesToTime:tile.appointment.startDate]];
CGFloat endPos = [APCalendarCurrentDayView yAxisForTime:[APCalendarCurrentDayView minutesToTime:tile.appointment.endDate]];
tile.frame = CGRectMake(kLeftSideBuffer, startPos, (self.bounds.size.width - kLeftSideBuffer) , endPos - startPos);
tile.backgroundColor = [UIColor colorWithHexString:tile.appointment.appointmentColor];
}
[sameTimeAppointments removeAllObjects];
for (UIView *view in self.subviews) {
APCalendarDayTile *tile = (APCalendarDayTile *)view;
if ([self viewIntersectsWithAnotherView:tile]) {
if ([sameTimeAppointments objectForKey:[NSString stringWithFormat:#"%f", tile.frame.origin.y]] != nil) {
NSMutableArray *tempArray = [[sameTimeAppointments objectForKey:[NSString stringWithFormat:#"%f", tile.frame.origin.y]] mutableCopy];
[tempArray addObject:tile];
[sameTimeAppointments setValue:tempArray forKey:[NSString stringWithFormat:#"%f", tile.frame.origin.y]];
} else {
[sameTimeAppointments setValue:[NSMutableArray arrayWithObject:tile] forKey:[NSString stringWithFormat:#"%f", tile.frame.origin.y]];
}
}
}
for (NSString *currentDict in sameTimeAppointments) {
NSArray *currentAppointments = [sameTimeAppointments objectForKey:currentDict];
float tileWidth = ((self.frame.size.width - kLeftSideBuffer) / [currentAppointments count]);
for (int i = 0; i < [currentAppointments count]; i++) {
APCalendarDayTile *tile = [currentAppointments objectAtIndex:i];
float xPos = 0.0 + kLeftSideBuffer;
if (i != 0) {
xPos = (((APCalendarDayTile *)[currentAppointments objectAtIndex:i - 1]).frame.origin.x + tileWidth);
}
tile.frame = CGRectMake(xPos, tile.frame.origin.y, tileWidth, tile.frame.size.height);
[self bringSubviewToFront:tile];
}
}
}
- (BOOL)viewIntersectsWithAnotherView:(UIView*)selectedView{
NSArray *subViewsInView=[self subviews];// I assume self is a subclass
// of UIViewController but the view can be
//any UIView that'd act as a container
//for all other views.
for (UIView *theView in subViewsInView){
if (![selectedView isEqual:theView]) {
if(CGRectIntersectsRect(selectedView.frame, theView.frame)) {
if ((selectedView.frame.origin.y == theView.frame.origin.y) && (selectedView.frame.size.height == theView.frame.size.height)) {
return YES;
}
}
}
}
return NO;
}