UICollectionView horizontal continuous loop - uiscrollview

So I have a UICollectionView with custom UIViews for each cell. The UIView takes up the whole screen and you can horizontally navigate to each of the 4 different UIView/cells. This all works fine and uses a pager to indicate which page of the collectionview you are on.
I have looked around google and the like to see if I could get it to loop back to the beginning after you navigate passed the last cell. So for example:
I start at the 3rd cell, navigate to the 4th, then when i swipe to the right, it will go back to the first cell (pager will reflect that you are on the first page) and continue on from there.
Is this easy to do? Or am I missing something?
Thanks,
Jake

You only need 3 UIViews, to hold the image to the left of your current view, the image to the right of the current view, and the middle view, the one that is currently onscreen.
So lets say we have UIImages A B C D and UIViews 1 2 and 3
We are looking at View 2, image B. Page left will take us to image A, page right to image C.
As you swipe left/ page right, view 3 becomes the onscreen view with image C. When paging comes to rest, you swap the views contents around so the user is actually looking at the middle UIView2 again, with image C. View 1 has image B, view 3 has image D.
Scroll right again, and do the same shuffle. Now you have
View 1 -> image C
View 2 -> image D
View 3 -> image A
Next page right
View 1 -> image D
View 2 -> image A
View 3 -> image B
so on, ad infinitum in either direction
There is a nice WWDC video on this subject from 2011. Its worth digging out. It's the UIScrollView demo video (Documentation as PDF) You don't need Collection Views to do this, although there is no reason why you shouldn't...

Alright ready for this hotness?
So in my main view controller's viewDidLoad I identify the views to load into the uicollectionview cells in an array:
NSArray *vcs = [#"view5", #"view1", #"view2", #"view3", #"view4", #"view5", #"view1"]
I also set our pagecontrol count to:
self.pageControl.numberOfPages = vcs.count - 2; // for the fake first and last cells
I then instantiate the viewcontrollers in the main view controller's viewDidLoad as well as set the alpha for the collectionview to 0. Then in our scrollViewDidEndDecelerating I handle the Carousel (go from last cell to first and first cell to last) and updating the pageControl to reflect the "real" page number.
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
// Snaps into the cell
CGFloat pageWidth = scrollView.frame.size.width;
float fractionalPage = scrollView.contentOffset.x / pageWidth;
NSInteger page = lround(fractionalPage);
self.pageControl.currentPage = page - 1; // because our first cell is the last cell for animation purposes only
// Carousel from first to last and last to first while updating pagecontrol
if (page == 0) {
[self.collectionView scrollToItemAtIndexPath:[NSIndexPath indexPathForRow:self.activeGaugeClusters.count - 2 inSection:0]
atScrollPosition:UICollectionViewScrollPositionLeft
animated:NO];
// Set our pagecontrol circles to the appropriate page indicator
self.pageControl.currentPage = self.activeGaugeClusters.count - 2;
} else if (page == self.activeGaugeClusters.count -1) {
[self.collectionView scrollToItemAtIndexPath:[NSIndexPath indexPathForRow:1 inSection:0]
atScrollPosition:UICollectionViewScrollPositionLeft
animated:NO];
self.pageControl.currentPage = 0;
}
}
Then in the collectionview implementation I do the following:
numberOfItemsInSection = vcs.count
and viewDidAppear for loading the first cell (whether it is the cell at indexPath.row == 1 or 2 or 3... (1 being the first real page not 0)
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[self.collectionView scrollToItemAtIndexPath:[NSIndexPath indexPathForRow:self.lastGaugePanel inSection:0]
atScrollPosition:UICollectionViewScrollPositionLeft
animated:NO];
self.pageControl.currentPage = self.lastGaugePanel - 1;
[UIView animateWithDuration:0.5 animations:^{
self.collectionView.alpha = 1.0;
}];
}
I think that about sums it up. The rest of the implementations for both the main vc and the collectionview are standard. So now I have a collection view that loads up other views and their VCs that each have their own animation or whatever. I save the indexPath.row to NSUserDefaults so that the cell that I was in when I leave is the first cell to show on the next load.
Hopes this helps someone, I know it did for me, unfortunately the use of 3rd party libraries was discouraged so I had to build this bad boy up (with help from #He Was, thanks again!)

Related

tableview update cells at wrong indexpath

i´ve got a problem with tableview.
i have a list of audio track and i check every track if this track actually exists. if not, i set a deleted image over the artwork in cells image view.
//SET DELETED IMAGE IF TRACK NOT EXISTS
if (!trackExists && cell.trackImageView.subviews.count < 1) {
UIImageView *deletedImageView = [[UIImageView alloc]initWithFrame:CGRectMake(0, 0, 60, 60)];
deletedImageView.image = [self resizeImageWithImage:[UIImage imageNamed:#"cellDeletedImage"] scaledToSize:CGSizeMake(60, 60)];
[cell.trackImageView addSubview:deletedImageView];
}
sometimes when i reload the tableview, the deleted image is on existing tracks. i know there are more efficient ways to do this, but i tried and tried and i ended up here.
also i logged this method, and it puts the deleted image even when there is no log.
Chances are, that if you're using table views as they are designed, that you are reusing cells. Does this happen when you scroll the table up and down a few times too? Does one cell that has the deleted image suddenly not have it if you scroll again?
It sounds like the reused cell still has a reference to that deleted image.
It's not quite clear where you add that image view, but I suggest that you move that code to the custom class for your table view cell (or create one if it doesn't exist). Subsequently in prepareForReuse, ensure that the image view is removed or hidden.
#class TrackTableViewCell : UITableViewCell
{
- (void) prepareForReuse
{
//Remove / hide image view
}
- (void) setAsDeleted
{
//Add / show image view
}
}
(Forgive objective-c syntax errors! I've been using Swift for a while now.)

How to expand table view cell and show content beneath it?

I want to have the table view cell expand and show the buttons that I have laid out below the visible view when the cell isn't selected. So far I have managed to expand the cell so that the entire view shows with the buttons, but there is one major problem with this....
The buttons that are supposed to be revealed only when the cell is selected always appear in the table, and the table view looks really weird becuase for each cell there are buttons overlapping the next cell which were supposed to be hidden!
I have tired making a subclass of the cell, but I am stuck because when I override the setSelected method to show the button, all the buttons from all the cells show up, not just the one I clicked, Ill provide my code below.
I there an easier way to show the buttons without using a subclass? And if not how could I use the subclass in a way that wouldn't show all the buttons for all the cells?
Cell Subclass (.m file)
- (void)awakeFromNib {
// Initialization code
editHidden.hidden = YES;
removeHidden.hidden = YES;
}
- (void)setSelected:(BOOL)selected animated:(BOOL)animated {
[super setSelected:selected animated:animated];
editHidden.hidden = NO;
removeHidden.hidden = NO;
// Configure the view for the selected state
}
Your table view delegate needs to implement tableView:heightForRowAtIndexPath:. Implement this method to return the correct height for your cell given the state that it is in (collapsed or expanded). When it comes time to expand your cell you should update your state and call [tableView beginUpdates]; [tableView endUpdates]; to have it recalculate and relayout the tableview.

Continuous scroll with UIScrollView Xcode

How do I make a scroll view start over again?
IE: my UIScrollView is 1000px wide. The user will swipe through the view to find a topic. When the user reaches the last topic (say topic 10) how do I have the UIScrollView start back at the beginning (topic 1) without having to scroll backwards?
So, the view will just continue in a circle forever, if the user wishes.
This is the method that you are going to use:
- (void)setContentOffset:(CGPoint)contentOffset animated:(BOOL)animated
after you detect that user reaches the last topic.
I am assuming you are using a paging UIScrollView with topics as pages. The trick is to duplicate the end pages, and then when the user swipes to the last page in the scrollview (which is a duplicate of the first page) and the deceleration stops you want to change the content offset back to the first page.
When I needed to do this, I found this post. I used setContentOffset in scrollViewDidEndDecelerating rather than scrollRectToVisible, but either approach will work.
- (void)scrollViewDidEndDecelerating:(UIScrollView *)sender
{
if (scrollView.contentOffset.x == 0) {
[scrollView setContentOffset:contentOffsetOfLastTopic animated:NO];
}
else if (scrollView.contentOffset.x == contentOffsetOfLastPage) {
[scrollView setContentOffset:contentOffsetOfFirstTopic animated:NO];
}
}

NSSplitView resizes the custom NSView contained

I've a vertical NSSplitView, the bottom subview contains a custom view (eg NSView) and a NSTextView.
The NSView contains inside it two NSButtons.
When I resize the splitView, making it smaller, the NSView containing the buttons is resized, too.
I don't want this behavior.
To better explain my problem please view the attached image.
Image 1: the window at application startup, everything is ok
Image 2: I've resized making smaller the split view, only a little part of buttons is visible
Image 3: I've enlarged again the split view but as you can see the NSView remains smaller and buttons are no longer visible (if I resize the splitView to bottom the NSView 'disappears')
This is a vicious problem that's based on the legacy workings of Cocoa views. The best solution I've seen is to constrain the minimum dimension of any portion of the split view. If the subviews never collapse, their metrics don't cross into another dimension and they should re-enlarge just fine.
To do this, set up a delegate for your split view, which will implement - splitView:constrainMaxCoordinate:ofSubviewAt:. The split view will call your delegate method hoping it can leave the max divider position at the height of the split view (passing this in as the second argument), but you can simply subtract some quantity from that value (say, 60) to return it as the minimum height for the bottom view.
- (CGFloat)splitView:(NSSplitView *)aSplitView
constrainMaxCoordinate:(CGFloat)proposedMin
ofSubviewAt:(NSInteger)dividerIndex {
return proposedMin - 60;
}
Of course, you'll probably want to do more checking in this method to make sure you're talking about the right split view, and the right subview, to avoid overreaching effects, but this is the basic idea.
(See also this fabulicious article on the subject.)
Constraining the divider position did not help in my case, as I'm animating the subviews and subviews can be collapsed.
I managed to achieve an acceptable solution by implementing the splitView delegate method -splitviewWillResizeSubviews: (means, you have to connect the delegate property from the split view to your controller in IB or in code) to maintain a minimum width by setting the subview to hidden instead of shrinking it to zero:
- (void)splitViewWillResizeSubviews:(NSNotification *)notification {
NSUInteger divider = [[[notification userInfo] valueForKey:#"NSSplitViewDividerIndex"] intValue];
NSView *subview = nil;
if(divider == SPLITVIEW_DIVIDER_SIDEBAR) {
subview = (NSView*)[self.splitView.subviews objectAtIndex:SPLITVIEW_SIDEBAR_INDEX];
}
if(subview) {
if(subview.frame.size.width < SPLITVIEW_MINIMUM_SIDEBAR_WIDTH) {
CGRect correctedFrame = subview.frame;
correctedFrame.size.width = SPLITVIEW_MINIMUM_SIDEBAR_WIDTH;
subview.frame = correctedFrame;
subview.hidden = YES;
} else {
subview.hidden = NO;
}
}
}

Finder-Style floating group rows in view-based NSOutlineView

I have implemented a view-based NSOutlineView in my project. I am using floating group rows. Now, I would like to have this NSOutlineView look basically like the Finder list-view (CMD-2) when it is in the "arranged-by" layout (e.g. "by kind": CTRL-CMD-2). That means, the top-most group row should display the column titles and as soon as the next lower group row is starting to nudge the previous one out of the view, the column titles fade in on the second group row (I hope this makes sense).
Is there any out-of-the-box way to achieve this? So far I have successfully subclassed NSTableCellView to show the columns' titles, however, I cannot get the fade-in to work as I cannot seem to find out the position of the group row in relation to the floating one above it.
Regards,
Michael
I've found a possible way to achieve what I want. In my custom NSTableCellView's drawRect: method, it's of course possibly in a nasty way to find out the view's position relative to the enclosing NSClipView. The relevant code:
- (void)drawRect:(NSRect)dirtyRect {
// _isGroupView is a member variable which has to be set previously
// (usually in setObjectValue:) in order for us to know if we're a
// group row or not.
if (_isGroupView) {
// This is the nasty party:
NSClipView *clipView = (NSClipView*)self.superview.superview.superview;
if (![clipView isKindOfClass:[NSClipView class]]) {
NSLog(#"Error: something is wrong with the scrollbar view hierarchy.");
return;
}
NSRect clipRect = [clipView documentVisibleRect];
CGFloat distanceToTop = self.superview.frame.origin.y - clipRect.origin.y;
if (self.superview.frame.origin.y - clipRect.origin.y < self.frame.size.height) {
// This means, that this NSTableCellView is currently pushing the
// view above it out of the frame.
CGFloat alpha = distanceToTop / self.frame.size.height;
NSColor *blendColor = [[NSColor blackColor] blendedColorWithFraction:alpha ofColor:[NSColor whiteColor]];
// ...
// do stuff with blendColor
// ...
// blendColor should now be the appropriate color for the wanted
// "fade in" effect.
//
}
}
}
I hope this makes sense ;-). Any tips are still appreciated!
Cheers,
Michael

Resources