Setting scroll indicator insets on NSCollectionView - macos

I know UIScrollView has scrollIndicatorInsets, so if I had a UICollectionView I could do something like
collectionView.scrollIndicatorInsets = UIEdgeInsetsMake(0,0,0,0);
But how can I set the scroll indicator insets on an NSCollectionView since NSScrollView doesn't appear to have a scrollIndicatorInsets property?

Collection view is enclosed in Clip view which itself enclosed in Scroll View which has the following:
collectionView.enclosingScrollView?.scrollerInsets
Which are declared:
/* The distance the scrollers are inset from the edge of the scroll view. */
#available(OSX 10.10, *)
public var scrollerInsets: NSEdgeInsets
You can also access the scroller object itself if you need futher customizations:
collectionView.enclosingScrollView?.verticalScroller

Related

Subview loaded inside UIView does not constrain to UIView bounds

I have created the following scene:
Application scene
The scene contains a stackView with all other views contained inside of it. The grey area is a UIView which gets three subviews added to it during viewDidLoad. I want each of these views to fill the UIView and be constrained to its bounds. When one of the three buttons is clicked, the corresponding view is brought to the front of for display. The issue I am facing is that the subviews are not being constrained to the UIView. I've tried quite a few different options but cannot seem to get the effect I am looking for. The red in the following image is the background of one of the loaded subviews.
iPhone 5
On larger devices, the subviews overextend the boundary on the right side as well. How do I constrain the subViews to my UIView boundary?
Set the frame of the 3 views you want to be constrained with the bounds of the grey superview. This can be done in layoutSubviews of the grey view or in viewDidLayoutSubviews of the viewController.
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
subview0.frame = greyView.bounds
subview1.frame = greyView.bounds
subview2.frame = greyView.bounds
}
or within the custom class for the grey view (if you have defined a custom class)
override func layoutSubviews() {
super.layoutSubviews()
subview0.frame = bounds
subview1.frame = bounds
subview2.frame = bounds
}
Can also be done by using constraints to constrain the subviews to their superview.

Update the size of an NSCollectionViewItem when its parent NSCollectionView changes size

I have an NSCollectionView which has NSCollectionViewItem's as child objects. I have the flow layout of the NSCollectionView set the width of the items to fill the width of the view when the application is launched, but if I resize the window, the items stay the original width and no longer fill the view. Is there some way that I can have this resizing (fill width) happen automatically or is there a method that is called when the collection view is resized (from window resize) that I can access to set the width of the items to fill the view?
All you have to do is to override the collection view class like this:
class MyCollectionView: NSCollectionView {
override var frame: NSRect {
didSet {
collectionViewLayout?.invalidateLayout()
}
}
}
This ensures that whenever the frame of the collection view changes, it invalidates the layout.

Can UIPopoverPresentationController be forced to reposition popover instead of resizing it?

I am using auto layout with Storyboard. I present a popoverPresentationController from a cell rect:
NumberController * viewController = [self.storyboard instantiateViewControllerWithIdentifier:#"NumberController"];
UIPopoverPresentationController *pc = [viewController popoverPresentationController];
pc.delegate = self;
pc.permittedArrowDirections = UIPopoverArrowDirectionAny;
pc.sourceView = tableView;
pc.sourceRect = [tableView rectForRowAtIndexPath:indexPath];
[self.navigationController presentViewController:viewController animated:animated completion:nil];
The popover presents on an iPad in portrait mode with the arrow up.
I rotate the iPad to landscape mode. The popoverPresentationController keeps the same sourceView/sourceRect and properly points to the cell. It also keeps the up arrow.
But it is now at the bottom of the view, so the popover resizes to a shorter height. This is not desired behavior.
If the popover were simply to move to a new position and change the arrow direction, it would not need to resize at all. This is the desired behavior.
I thought the following method might permit me to make changes, but it is not called since the sourceView rect does not change:
- (void)popoverController:(UIPopoverController *)popoverController
willRepositionPopoverToRect:(inout CGRect *)rect
inView:(inout UIView **)view {
}
I have tried to reset the permittedArrowDirections (in preferredContentSize, because this seemed like the most logical place). This does not work (the popover still resizes):
- (CGSize) preferredContentSize {
[super preferredContentSize];
self.popoverPresentationController.permittedArrowDirections = UIPopoverArrowDirectionUnknown;
return CGSizeMake(DEFAULT_POPOVER_WIDTH,DEFAULT_POPOVER_HEIGHT);
}
I simply cannot find a way to force the popoverPresentationController to change arrow direction and reposition the popover instead of resizing the popover. I am beginning to think it is not even possible - but I still hold out hope that I am just missing something.
EDIT: In the meantime, it has occurred to me that maybe a popover is not the best way to present this view if I don't want it resized in iPad. I am going to try it with UIModalPresentationFormSheet presentation. But I would still like to find an answer to this question.
I just ran into the problem where
- (void)popoverController:(UIPopoverController *)popoverController
willRepositionPopoverToRect:(inout CGRect *)rect
inView:(inout UIView **)view {
was not being called because my view controller was detached. There may be a view in your view hierarchy whose view controller has not been added as a child view controller.
I thought the following method might permit me to make changes, but it is not called since the sourceView rect does not change
The sourceView rect does not have to change, just the interface orientation. From the UIPopoverControllerDelegate documentation:
For popovers that were presented using the presentPopoverFromRect:inView:permittedArrowDirections:animated: method, the popover controller calls this method when the interface orientation changes.

Custom NSView in NSTableView not showing all subviews

I'm trying to create a custom NSView to display in a column in a view-based NSTableView. The view contains 2 subviews, an NSTextField and an NSButton. I want the button to stay the width set by the constraints, and the textfield to resize when the NSView is resized. Below is a small animation showing the NSView and its subviews, and the constraints I created.
As you can see, resizing the NSView works as expected.
Now, when displaying this custom NSView in an NSTableView, it looks as if the button just disappears, and resizing the column makes the textfield resize with it (the 'Category' column).
The coded used to create the NSView in tableView:viewForTableColumn:row:
let identifier = tableColumn!.identifier
if identifier == "Category" {
var view = tableView.makeViewWithIdentifier(identifier, owner: self) as? TableCategoryView
if view == nil {
view = TableCategoryView(frame: tableView.frame)
view!.identifier = identifier
}
return view
}
The strange thing is, when there are no constraints on the 2 views, the button and textfield are both happily displayed inside the column, but they then of course don't resize with the table column width.
What am I doing wrong?
EDIT: It looks like something else is wrong. The NSView itself isn't resizing at all with the table column.
I think TableCategoryView is your new class and it is subclassed from NSView and shall replace the NSTableCellView you get when creating (in IB) a view based NSTableView. If you really want to create your own TableCellView it should be a direct subclass of NSTableCellView not NSView.
But in your case (add a button to the TableCellView) you do not need to create a new class. The existing TableCellView object already has a TextField (a property) with the name textField. Then simply drag (means: add) a button into the existing TableCellView (resize it and set the constraints) and drag a link from the button to a corresponding method in the delegate of the TableView. In the "corresponding method" you can ask for the clicked row and column and identify the click button. I did so for a TableView and for me it works well.

How to control drawing of NSTableView in NSScrollView?

How can I get NSTableView to always show the same columns regardless of the horizontal scroller position? In the rightmost visible column I have custom cell views. I want the horizontal scroller to control what is being drawn in these custom views. The vertical scrolling should work normally.
I have tried several approaches without much success. For example, I can control the knob proportion of the horizontal scroller by making the table view wider, or by making the scroll view think its document view is actually wider than it is. One way is subclassing NSClipView and overriding -documentRect as follows:
-(NSRect)documentRect {
NSRect rect = [super documentRect];
rect.size.width += [[NSApp delegate] hiddenRangeWidth];
return rect;
}
However, while the scroller knob looks as it should and I can drag it right without moving the table view, when I start scrolling in another direction, the knob returns to the left edge. I also have the problem that I can't get the horizontal scroller to appear automatically. This happens with the original classes as well, not just with my custom clip view. Could these problems be related?
I have also tried replacing the document view with a custom view that acts as a proxy between the clip view and the table view. Its -drawRect: calls the table view's -drawRect:. However, nothing is drawn. I guess this is because the table view now has no superview. If the table view were added to this proxy view as a subview, it would move with it. How would I make it stationary in horizontal axis?
So, to reiterate:
What is the best way to make a table view scrollable, while always showing the same columns regardless of the horizontal scroller position?
What is the best way to get the scroller position and knob proportion? Should I add an observer for the NSViewBoundsDidChangeNotification from NSClipView?
I finally managed to solve the problem by letting the scroll view and table view behave normally, and adding an NSScroller. In order to make hiding the scroller easier, I decided to use Auto Layout and add it in Interface Builder. (The Object library doesn't include a scroller, but you can add a custom view and set its class to NSScroller.) I set the height of the scroller as a constraint, and bound the scroller and the constraint to outlets in code:
#property (nonatomic, retain) IBOutlet NSScroller *scroller;
#property (nonatomic, unsafe_unretained) IBOutlet NSLayoutConstraint *scrollerHeightConstraint;
Now I can make the scroller visible or hide it when necessary:
if (_zoomedIn) {
_scrollerHeightConstraint.constant = [NSScroller scrollerWidthForControlSize:NSRegularControlSize scrollerStyle:NSScrollerStyleOverlay];
[_scroller setKnobProportion:(_visibleRange / _maxVisibleRange)];
[_scroller setDoubleValue:_visibleRangePosition];
[_scroller setEnabled:YES];
} else {
_scrollerHeightConstraint.constant = 0.0;
}
Here the properties visibleRange, maxVisibleRange and visibleRangePosition are the length of the visible range (represented by the scroller knob), the total range (represented by the scroller slot), and the start of the visible range (the knob position), respectively. These can be read by binding the scroller's sent action to the following method in Interface Builder:
- (IBAction)scrollAction:(id)sender {
switch (self.scroller.hitPart) {
case NSScrollerNoPart:
break;
case NSScrollerDecrementPage:
_visibleRangePosition = MAX(_visibleRangePosition - _visibleRange / _maxVisibleRange, 0.0);
self.scroller.doubleValue = _visibleRangePosition;
break;
case NSScrollerIncrementPage:
_visibleRangePosition = MIN(_visibleRangePosition + _visibleRange / _maxVisibleRange, 1.0);
self.scroller.doubleValue = _visibleRangePosition;
break;
case NSScrollerKnob:
case NSScrollerKnobSlot:
_visibleRangePosition = self.scroller.doubleValue;
break;
default:
NSLog(#"unsupported scroller part code %lu", (unsigned long)self.scroller.hitPart);
}
// Make the custom cell views draw themselves here.
}
In order to get the scrolling work with gestures, we need to implement -scrollWheel: in the custom cell view class:
- (void)scrollWheel:(NSEvent *)event {
if (event.deltaX != 0.0) {
NSScroller *scroller = appDelegate.scroller;
if (scroller.isEnabled) {
double delta = event.deltaX / (NSWidth(scroller.bounds) * (1.0 - scroller.knobProportion));
scroller.doubleValue = MIN(MAX(scroller.doubleValue - delta, 0.0), 1.0);
}
}
if (event.deltaY != 0.0) {
[self.nextResponder scrollWheel:event];
}
}
I thought I could've just passed the event to the scroller, but apparently it doesn't handle the event. The above code doesn't seem to handle bounce back, and momentum scrolling doesn't always work. Sometimes the knob just halts in the middle of the motion. I believe this has to do with the scroller style being NSScrollerStyleLegacy by default. Setting it to NSScrollerStyleOverlay would require changes to the layout, so I haven't tried it yet.
Another problem is that the scrollers don't blend into each other in the corner like they do in a scroll view (see below). Maybe NSScrollerStyleOverlay would fix this, too.

Resources