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];
}
}
Related
(Similar to this question, which is unanswered: Tableview scroll to top not going all the way, as well as this one, also unanswered: Show navigation bar's large title and search bar on scroll to top collection view iOS 11 Swift 4)
I am trying to replicate scrolling to the top of a UITableView on status bar tap, but when I've tapped the UITabBar item and I'm already in that view. I have the tapping part working, but the scrolling to top is not working as I want it to.
The tableview is embedded in a navigationbar with large titles, and the searchbar is in the header, so it expands and collapses with scroll (the default behavior).
I am using the following to scroll, which, as expected, scrolls to the first table view cell, not expanding the navigation bar and/or the search bar:
[tv scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]
atScrollPosition:UITableViewScrollPositionTop animated:YES];
I can't seem to figure out how to scroll to the top and expand the navigation bar and the search bar. Manually calculating the content offset of the tableview doesn't work, as the offsets of the tableview are obviously different when the tableview is scrolled. Furthermore, I can't store the offset, as different screen sizes have different content offsets for an expanded navigationbar and searchbar.
Has anyone been able to figure this out?
Well, I'm a bit late to the action here but for future reference:
For iOS 10+ (sufficient, since growing navigationBars were introduced in 11) you can use
scrollView.perform(NSSelectorFromString("_scrollToTopIfPossible:"), with: true)
This is private API. I've searched for a couple of hours and tried a lot of stuff but in the end this is the only thing that worked. Note that WhatsApp seems to be using the same method. Maybe they have a special deal with Apple or hide the api call (cough base64 cough).
#implementation UIWindow (SCROLL)
- (void)performScrollToTop {
SEL selector = NSSelectorFromString(#"_scrollToTopViewsUnderScreenPointIfNecessary:resultHandler:");
if ([self respondsToSelector:selector] == false) {
return;
}
NSMethodSignature *signature = [UIWindow instanceMethodSignatureForSelector:selector];
if (signature == nil) {
return;
}
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[UIWindow instanceMethodSignatureForSelector:selector]];
[invocation setTarget:self];
[invocation setSelector:selector];
CGRect statusBarFrame = UIApplication.sharedApplication.statusBarFrame;
CGPoint point = CGPointMake(statusBarFrame.size.width / 2.0, statusBarFrame.size.height + 1.0);
[invocation setArgument:&point atIndex:2];
[invocation invoke];
}
#end
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.)
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;
}
}
}
Does anyone know a way to detect when an NSScrollView is scrolled by user input, and only user input)?
The reason I want to do this is because I have a NSScrollView with a contentView that is continuously increasing it's width. I want the NSScrollView to 'lock' onto the right hand end of the contentView (i.e. track it) if the user scrolls to the right hand end of the contentView and I want the 'lock' to be released when the user (and only the scrolls) scrolls aways from the right hand end.
The closest I had to getting to this to work was by observing the NSViewBoundsDidChangeNotification and changing a 'lock' variable, as shown here:
- (void)drawRect:(NSRect)dirtyRect
{
(...)
if (lockToEnd) {
NSLog(#"xAxisView at end");
NSPoint newScrollOrigin;
newScrollOrigin.y = 0;
newScrollOrigin.x = [self frame].size.width - [[self enclosingScrollView] bounds].size.width;
[self scrollPoint:newScrollOrigin];
}
}
-(void)SWXAxisViewDidScroll:(NSNotification *)note{
NSLog(#"XAxisDidScroll: %#",note);
if ([[[self enclosingScrollView] horizontalScroller] floatValue] > 0.97){
lockToEnd = YES;
} else {
lockToEnd = NO;
}
}
However, this was not appropriate because an NSViewBoundsDidChangeNotification is sent anytime the bounds are changed, and thus when the bounds of the contentView increase, the NSScroller reduces it's floatValue and my observing method is called. EVen if I set the NSScroller's floatValue to 1.0, it is reset to 0.0 when the bounds.size.width of the contentView first exceeds the bounds.size.width of the NSScrollView. Thus, I can't tell if the NSViewBoundsDidChangeNotification was sent because the user scrolled or because the contentView got wider.
I have considered subclassing NSScroller and using the mouseDown: and mouseDragged: methods to track user input and update my lock variable. However, my concern is that these methods will not be called if the user swipes their trackpad to scroll. Another smaller concern, which I think is probably unfounded, is that it might break the NSScrollView<->NSScroller relationship and I would have to re-implement a lot of scrolling features.
Have I missed a simpler way to do this? It seems like I should be able to do this because documents do it all the time? Are my concerns about subclassing NSScroller valid?
I am looking to inset the contents of an NSTableView so that there is a gap between the top of the table view and the first cell.
On iOS this is easy with UITableView - achieved by using setContentInset:.
Turn headers back on and substitute the header view with your own subclass. Override its -drawRect: to draw only your background color. Also override -headerRectOfColumn: to prevent any of the column headers from being drawn. I'm not sure if this prevents column dragging or sorting but I'll bet it does.
The question asked how to adjust content insets similar to iOS. The currently selected answer shows how to move the first row down, but that's not quite the same thing. Adjusting the content insets will also move the start of the scrollbar to the inset position, just like iOS. This is useful when placing content underneath a "vibrant" or transparent toolbar.
An NSTableView itself does not have content insets. On macOS content insets are usually part of NSScrollView. To get access to the scroll view of NSTableView's view controller you can use the enclosingScrollview method of NSView, disable automatic adjustment and set the insets like this:
(warning old school Obj-C here)
self.enclosingScrollView.automaticallyAdjustsContentInsets = NO;
self.enclosingScrollView.contentInsets = NSEdgeInsetsMake(50.f,0.f,0.f,0.f);
Calling these from viewDidLoad is usually fine, however some types of table views will override your values with their own.
NSOutlineView set to source-list mode comes with lots of default values overridden to make the view look like the Finder sidebar.
There is no "clean" way to set the content-insets of these views. They stubbornly override your values, I've found that if you subclass NSOutlineView and overload setFrameSize: it will do the trick. So like this (inside the NSOutlineView subclass):
- (void)setFrameSize:(NSSize)newSize {
[super setFrameSize:newSize];
self.enclosingScrollView.automaticallyAdjustsContentInsets = NO;
self.enclosingScrollView.contentInsets = NSEdgeInsetsMake(100.f,0.f,0.f,0.f);
}
This will do the trick, but the initial scroll position will be strange. Calling scrollToBeginningOfDocument: from the initWithCoder: method of the subclass will scroll it to the correct initial position.
It's not very clean but you can achieve that by having the first row higher than the rest. Implement heightOfRow table delegate method:
- (CGFloat)tableView:(NSTableView *)tableView heightOfRow:(NSInteger)row
{
if (row == 0) {
return normalRowHeight + topPadding;
} else {
return normalRowHeight;
}
}
The drawback is that you would also need to implement custom highlighting and custom cell drawing to take into account the extra space for the first row.
scrollView.automaticallyAdjustsContentInsets = false
scrollView.contentInsets = NSEdgeInsets(top: 40, left: 0, bottom: 0, right: 0)