how to make tableView offset become zero? - uiscrollview

I have a TableView, that Have so many TableViewCell, but in the end of TableView I want to make a code that will make tableview offset become zero, so the tableview back to the top
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
if (scrollView.contentOffset.y <=-30) {
[self dosomething];
}
else if(scrollView.contentOffset.y >=1150){
[tableViewA setContentOffset:CGPointZero animated:YES];//this code is called and yet offset doesn't become 0
}
}
so with this code, if user bounce to the top, it will call dosomething function, and if we bounce to the bottom, it will make a tableViewA at the top, but it's still at bottom. if I am not wrong, the tableView Bounce again to bottom. any one can give me a clue?

I tried your code and find that the tableview did set it's contentOffset to (0,0) but It will continue the drag animation.Seems that during the scroll animation, the tableview animates and changes it's bounds at the same time in order to call layoutSubviews every time the bounds change. So even the contentOffset was set to (0,0) but just after that the bounds was set to a new value to continue the scroll animation.
Maybe you can try
- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView {
[self setContentOffset:CGPointZero animated:YES];
}

Related

Automatically wrap NSTextField using Auto Layout

How does one go about having auto-layout automatically wrap an NSTextField to multiple lines as the width of the NSTextField changes?
I have numerous NSTextFields displaying static text (i.e.: labels) in an inspector pane. As the inspector pane is resized by the user, I would like the right hand side labels to reflow to multiple lines if need be.
(The Finder's Get Info panel does this.)
But I haven't been able to figure out the proper combination of auto layout constraints to allow this behavior. In all case, the NSTextFields on the right refuse to wrap. (Unless I explicitly add a height constraint that would allow it to.)
The view hierarchy is such that each gray band is a view containing two NSTextFields, the property name on the left and the property value on the right. As the user resizes the inspector pane, I would like the property value label to auto-resize it's height as need-be.
Current situation:
What I would like to have happen:
(Note that this behavior is different than most Stack Overflow questions I came across regarding NSTextFields and auto layout. Those questions wanted the text field to grow while the user is typing. In this situation, the text is static and the NSTextField is configured to look like a label.)
Update 1.0
Taking #hamstergene's suggestion, I subclassed NSTextField and made a little sample application. For the most part, it now works, but there's now a small layout issue that I suspect is a result of the NSTextField's frame not being entirely in sync with what auto-layout expects it to be. In the screenshot below, the right-hand side labels are all vertically spaced with a top constraint. As the window is resized, the Where field is getting properly resized and wrapped. However, the Kind text field does not get pushed down until I resize the window "one more pixel".
Example: If I resize the window to just the right width that the Where textfield does it's first wrap, then I get the results in the middle image. If I resize the window one more pixel, then the Kind field's vertical location is properly set.
I suspect that's because auto-layout is doing it's pass and then the frames are getting explicitly set. I imagine auto-layout doesn't see that on that pass but does it it on the next pass, and updates the positions accordingly.
Assuming that's the issue, how do I inform auto-layout of these changes I'm doing in setFrameSize so that it can run the layout again. (And, most importantly, not get caught in recursive state of layout-setFrameSize-layout-etc...)
Solution
I've come up with a solution that appears to work exactly how I was hoping. Instead of subclassing NSTextField, I just override layout in the superview of the NSTextField in question. Within layout, I set the preferredMaxLayoutWidth on the text field and then trigger a layout pass. That appears to be enough to get it mostly working, but it leaves the annoying issue of the layout being briefly "wrong". (See note above).
The solution to that appears to be to call setNeedsDisplay and then everything Just Works.
- (void)layout {
NSTextField *textField = ...;
NSRect oldTextFieldFrame = textField.frame;
[textField setPreferredMaxLayoutWidth:NSWidth(self.bounds) - NSMinX(textField.frame) - 12.0];
[super layout];
NSRect newTextFieldFrame = textField.frame;
if (oldTextFieldFrame.size.height != newTextFieldFrame.size.height) {
[self setNeedsDisplay:YES];
}
}
The simplest way to get this working, assuming you're using an NSViewController-based solution is this:
- (void)viewDidLayout {
[super viewDidLayout];
self.aTextField.preferredMaxLayoutWidth = self.aTextField.frame.size.width;
[self.view layoutSubtreeIfNeeded];
}
This simply lets the constraint system solve for the width (height will be unsolvable on this run so will be what ever you initially set it to), then you apply that width as the max layout width and do another constraint based layout pass.
No subclassing, no mucking with a view's layout methods, no notifications. If you aren't using NSViewController you can tweak this solution so that it works in most cases (subclassing textfield, in a custom view, etc.).
Most of this came from the swell http://www.objc.io/issue-3/advanced-auto-layout-toolbox.html (look at the Intrinsic Content Size of Multi-Line Text section).
If inspector pane width will never change, just check "First Runtime Layout Width" in IB (note it's 10.8+ feature).
But allowing inspector to have variable width at the same time is not possible to achieve with constraints alone. There is a weak point somewhere in AutoLayout regarding this.
I was able to achieve reliable behaviour by subclassing the text field like this:
- (NSSize) intrinsicContentSize;
{
const CGFloat magic = -4;
NSSize rv;
if ([[self cell] wraps] && self.frame.size.height > 1)
rv = [[self cell] cellSizeForBounds:NSMakeRect(0, 0, self.bounds.size.width + magic, 20000)];
else
rv = [super intrinsicContentSize];
return rv;
}
- (void) layout;
{
[super layout];
[self invalidateWordWrappedContentSizeIfNeeded];
}
- (void) setFrameSize:(NSSize)newSize;
{
[super setFrameSize:newSize];
[self invalidateWordWrappedContentSizeIfNeeded];
}
- (void) invalidateWordWrappedContentSizeIfNeeded;
{
NSSize a = m_previousIntrinsicContentSize;
NSSize b = self.intrinsicContentSize;
if (!NSEqualSizes(a, b))
{
[self invalidateIntrinsicContentSize];
}
m_previousIntrinsicContentSize = b;
}
In either case, the constraints must be set the obvious way (you have probably already tried it): high vertical hugging priority, low horizontal, pin all four edges to superview and/or sibling views.
Set in the size inspector tab in section Text Field Preferred Width to "First Runtime layout Width"
This works for me and is a bit more elegant. Additionally i've made a little sample project on Github
public class DynamicTextField: NSTextField {
public override var intrinsicContentSize: NSSize {
if cell!.wraps {
let fictionalBounds = NSRect(x: bounds.minX, y: bounds.minY, width: bounds.width, height: CGFloat.greatestFiniteMagnitude)
return cell!.cellSize(forBounds: fictionalBounds)
} else {
return super.intrinsicContentSize
}
}
public override func textDidChange(_ notification: Notification) {
super.textDidChange(notification)
if cell!.wraps {
validatingEditing()
invalidateIntrinsicContentSize()
}
}
}

UICollectionView fix paging position after rotation

I have a UICollectionView with the frame of
[UIScreen mainScreen].bounds
and these attributes:
_collectionView.pagingEnabled = YES;
_collectionView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
A cell has also the size of the collection view. When I rotate, the contentOffset of the collection view does not fit to the new orientation. It has still the same offset as before the rotation.
To fix this, I changed the contentOffset manually in the didRotate method.
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation {
_collectionView.contentOffset = CGPointMake(_newContentOffsetX, _collectionView.contentOffset.y);
}
This works, but it looks terrible. I also tried to scroll to the current IndexPath, but it hast the same ugly behaviour:
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation {
[_collectionView scrollToItemAtIndexPath:_currentIndexPath atScrollPosition:UICollectionViewScrollPositionLeft animated:NO];
}
I need a clean transition and behaviour of updating the collection view's content offset when rotating the device.
Its better To use the
willAnimateRotationToInterfaceOrientation:duration:
When this method is called the bound of your view controllers view are already updated to the current device orientation. It seems that this method gets called inside of the rotation animation block. This means all the positions you will set inside of willAnimateRotationToInterfaceOrientation:duration: are animated.
This helps me a lot and I use it to update my view Controllers view content insets on rotation. Works like a charm! :-)
Instead of using didRotateFromInterfaceOrientation, try updating contentOffset in willRotateToInterfaceOrientation.
You'll need to make sure to compensate for using the other width/height dimension that you'll be rotating to since it won't have done the rotation yet.
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
_collectionView.contentOffset = CGPointMake(_newContentOffsetX, _collectionView.contentOffset.y);
}

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;
}
}
}

How to detect when a user, and only a user scrolls an NSScrollView

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?

NSScrollView scroll bars are of the wrong length

I have a Cocoa window, whose content view contains an NSScrollView that, in turns, contains a fixed-size NSView.
Upon launching the program, the scroll bars displayed initially are too small, as if the content size was much larger than it actually is:
When I start playing with, e.g., the vertical scroll bar, and bring it back to the original position at the top, it gets resized to its expected size (which corresponds to the ratio of scroll view and content view sizes):
(Notice the horizontal bar, which still has incorrect size. If I then play with it, and bring it back to its leftmost position, it gets resized to the correct size.)
I also encountered the same problem, I have searched everywhere but it seems no one else experiences this problem. Fortunately I found a hack which solves the problem.
What I did notice was that when the window is resized or maximized the scrollbars resize to the expected size (autoresizing has to be enabled). This is because when the window resizes so does the scrollview and the length of the scroll bars gets recalculated and is calculated correctly. Possibly due to some bug the scroll bar lengths are not calculated correctly on initialization. Anyway to fix the problem, in your application delegate create an outlet to your window. Override the "applicationDidFinishLaunching" method and inside it call the method "frame" on the window outlet, which returns the current NSRect of the window. Using the returned value add one to the size.width and size.height. The call the method setFrame with display set to YES. This will resize the window and force the size of the scrollbars to be recalculated.
Here is the code for applicationDidFinishLaunching Below
(void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
// Get the current rect
NSRect windowRect = [_window frame];`
// add one to the width and height to resize window
windowRect.size.width += 1;
windowRect.size.height += 1;
// resize window with display:YES to redraw window subviews
[_window setFrame:windowSize display:YES];
}
I encountered this issue when modifying an NSTextView textContainer size to toggle line wrapping. Resizing the enclosing view does cause the correct scroll view height to be used, however its a brutal solution.
NSScrollView supports -reflectScrolledClipView. Calling this directly in my case had no effect except when delayed on the runloop:
[textScrollView performSelector:#selector(reflectScrolledClipView:) withObject:textScrollView.contentView afterDelay:0];
The scroller position is correct but there is a scroller redraw. So it looks as if part of the view geometry is calculated when drawing. A better solution is therefore:
NSDisableScreenUpdates();
[textScrollView display];
[textScrollView reflectScrolledClipView:textScrollView.contentView];
[textScrollView display];
NSEnableScreenUpdates();
Building on the answer from jstuxx above, if you don't want the window to visibly resize, try:
NSRect windowRect = [[[self view] window] frame];
windowRect.size.width += 1;
windowRect.size.height += 1;
[[[self view] window] setFrame:windowRect display:YES];
windowRect.size.width -= 1;
windowRect.size.height -= 1;
[[[self view] window] setFrame:windowRect display:YES];
I had to put this code after where I was programmatically adding the scroll view to my interface.

Resources