Like the iBooks app, when you pull down the tableview, a search bar and segmented control appear, to allow you to search and switch between two types of views.
It sticks in that position when you pull down far enough, and alternatively, gets hidden when you pull the tableview up enough.
I am trying to implement the same thing with a UISegmentedControl.
So far I have added a segmented control successfully as a subview to the table. (It has a negative Y frame so make it stick above the tableview).
I have also implemented this code:
- (void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView {
float yOffset = scrollView.contentOffset.y;
if (yOffset < -70) {
[scrollView setContentOffset:CGPointMake(0.0f, -70.0f) animated:YES];
} else if (yOffset > -10) {
[scrollView setContentOffset:CGPointMake(0.0f, -11.0f) animated:YES];
}
}
This works great, until I try using the segmented control. Where the table will just act like it is scrolling, ignoring the segmented control altogether (i.e. if I tap on a segment, it doesn't even get selected, instead the table scrolls up, hiding the segmented control.
I did use the scrollViewDidScroll method but this made it buggy and the scrolling jumpy.
I also tried to make the segmented control's exclusiveTouch = YES, but this had no effect whatsoever.
I would be thankful for all help! thanks in advance!
Here is my code which works:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
//
// Table view
//
if ([scrollView isKindOfClass:[myTableView class]]) {
//
// Discover top
//
CGFloat topY = scrollView.contentOffset.y + scrollView.contentInset.top;
if (topY <= self.tableHeaderHeightConstraint.constant) {
[self setIsScrolledToTop:YES];
} else {
[self setIsScrolledToTop:NO];
}
}
}
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{
//
// Table view
//
if ([scrollView isKindOfClass:[myTableView class]]) {
//
// Toggle favourite category
//
if ([self isScrolledToTop]) {
//
// Show
//
} else {
//
// Hide
//
}
}
}
Edited the above code to make it a bit more generic, but syntactically its correct
Related
I have a UITableViewCell with the following hierarchy:
I use swipe and pan gestures to move the Container View to left, so that the trash button is visible, Similar to Instagram:
But, on cell selection The thrash button is visible:
EDIT:
Please Note:
The trash button is actually a custom button with a transparent png image. The background colour of the button is then set as red.
Please provide your valuable suggestions or a solution to this problem. Thanks!
As I didn't get a proper answer, I was forced to fix this issue as pointed out by #hoang Van Ha.
In the custom cell class:
I hid the button by overriding setSelected method as follows:
- (void)setSelected:(BOOL)selected animated:(BOOL)animated {
[super setSelected:selected animated:animated];
if (self.cellContainerView.frame.origin.x == 0) {
self.trashButton.hidden = YES;
}
}
at the beginning of the gesture methods, i.e. pan and swipe I show the button again and on completing the gesture, I check if the frame of the container view has origin zero. If so, the button should be hidden.
- (IBAction)handleSwipeGesture:(UISwipeGestureRecognizer *)recognizer
{
self.trashButton.hidden = NO;
if ( recognizer.direction == UISwipeGestureRecognizerDirectionRight ) {
[UIView animateWithDuration:0.2
animations:^{
self.cellContainerView.frame = CGRectMake(0, self.cellContainerView.frame.origin.y, 320, self.cellContainerView.bounds.size.height);
}
completion:^(BOOL finished) {
self.trashButton.hidden = YES;
}
];
}
}
In the controller class, in cell for row method, do the same process.
if (cell.cellContainerView.frame.origin.x == 0) {
cell.trashButton.hidden = YES;
}
Please feel free to comment, or even post a better answer!
I went through several posts on dragging but couldn't find an answer to my problem.
I can use the mouseDown and mouseUp events to track the current positions and redraw the resized pane. What I want is to show the real time movement of the pane. Everytime mouseDragged event is fired, y coordinate of the new location is taken and setFrame is called to redraw. The window seems to flicker and gets stuck finally (title bar goes out of bounds and hidden) as it seems to miss the final events in the run loop.
Is there a way to solve this problem?
The view has been implemented in the following way
NSSplitView (divided into sections left dock, right dock, etc.)
NSView is used to implement a sub view inside the dock
NSTableView is used inside the NSView to hold multiple "panels"
There can be several panels inside this table view (one below another)
I need to resize these panels by dragging the border line. For this I'm using an NSButton in the bottom.(I want to show a thicker separation line there)
Here is the code for mouseDown, mouseUp, mouseDragged callbacks, used to resize the panel
-(void)mouseDown:(NSEvent *)theEvent {
draggingInProgress = YES;
firstDraggingPointFound = NO;
}
-(void)mouseUp:(NSEvent *)theEvent {
if (!draggingInProgress) {
return;
}
NSPoint point = [self convertPoint: [theEvent locationInWindow] fromView: nil];
if (firstDraggingPointFound) {
[_delegate heightChanged:[NSNumber numberWithFloat:(point.y - previousDragPosition)]];
}
draggingInProgress = NO;
[_delegate heightChangingEnded]; //draggingInProgress is set to NO
}
-(void)mouseDragged:(NSEvent *)theEvent {
if (!draggingInProgress) {
return;
}
NSPoint point = [self convertPoint: [theEvent locationInWindow] fromView: nil];
if (firstDraggingPointFound) {
[_delegate heightChanged:[NSNumber numberWithFloat:(point.y - previousDragPosition)]];
} else {
firstDraggingPointFound = YES;
}
previousDragPosition = point.y;
}
//Delegate method
-(void)heightChanged:(NSNumber *)change {
NSRect f = [[self view] frame];
f.size.height += [change floatValue];
if (f.size.height < 100) {
f.size.height = 100;
}
[[self view] setFrame:f];
[self.panelViewModel setPanelHeight:f.size.height];
if (_heightChangeDelegate) {
[_heightChangeDelegate heightChangedForPanel:self];
}
[[self view] setFrame:f];
}
What would be the problem here?
Is there a better way to do this?
First off, I wouldn’t use the word “Panel” to describe what you want, since an “NSPanel” is a kind of “NSWindow”. Let’s call them “panes.”
Second, I wouldn’t use an NSTableView to contain your panes. NSTableView really isn’t designed for that, it’s for tabular data. You can use an NSStackView, which IS designed for it, or just use raw constraints and autolayout, manually setting the top of each pane to equal the bottom of the previous one.
I am working on a project that has the concept of draggable controls, everything is working fine except that NSView seems to employ a fade in/out animation when calling setHidden:.
I have been able to work around the problem by changing the line session.animatesToStartingPositionsOnCancelOrFail = YES; to NO and implementing the image snapback myself with a custom animated NSWindow subclass. it looks great, but I know there must be an easier way.
I have tried:
using NSAnimationContext grouping with duration of 0 around the setHidden: calls
setting the view animations dictionary using various keys (alpha, hidden, isHidden) on the control and superview
overriding animationForKey: for both the control and its superview
I am not using CALayers and have even tried explicitly setting wantsLayer: to NO.
Does anybody know how to either disable this animation, or have a simpler solution then my animated NSWindow?
here is my stripped down altered code with the bare minimum to see what I'm talking about.
#implementation NSControl (DragControl)
- (NSDraggingSession*)beginDraggingSessionWithDraggingCell:(NSActionCell <NSDraggingSource> *)cell event:(NSEvent*) theEvent
{
NSImage* image = [self imageForCell:cell];
NSDraggingItem* di = [[NSDraggingItem alloc] initWithPasteboardWriter:image];
NSRect dragFrame = [self frameForCell:cell];
dragFrame.size = image.size;
[di setDraggingFrame:dragFrame contents:image];
NSArray* items = [NSArray arrayWithObject:di];
[self setHidden:YES];
return [self beginDraggingSessionWithItems:items event:theEvent source:cell];
}
- (NSRect)frameForCell:(NSCell*)cell
{
// override in multi-cell cubclasses!
return self.bounds;
}
- (NSImage*)imageForCell:(NSCell*)cell
{
return [self imageForCell:cell highlighted:[cell isHighlighted]];
}
- (NSImage*)imageForCell:(NSCell*)cell highlighted:(BOOL) highlight
{
// override in multicell cubclasses to just get an image of the dragged cell.
// for any single cell control we can just make sure that cell is the controls cell
if (cell == self.cell || cell == nil) { // nil signifies entire control
// basically a bitmap of the control
// NOTE: the cell is irrelevant when dealing with a single cell control
BOOL isHighlighted = [cell isHighlighted];
[cell setHighlighted:highlight];
NSRect cellFrame = [self frameForCell:cell];
// We COULD just draw the cell, to an NSImage, but button cells draw their content
// in a special way that would complicate that implementation (ex text alignment).
// subclasses that have multiple cells may wish to override this to only draw the cell
NSBitmapImageRep* rep = [self bitmapImageRepForCachingDisplayInRect:cellFrame];
NSImage* image = [[NSImage alloc] initWithSize:rep.size];
[self cacheDisplayInRect:cellFrame toBitmapImageRep:rep];
[image addRepresentation:rep];
// reset the original cell state
[cell setHighlighted:isHighlighted];
return image;
}
// cell doesnt belong to this control!
return nil;
}
#pragma mark NSDraggingDestination
- (void)draggingEnded:(id < NSDraggingInfo >)sender
{
[self setHidden:NO];
}
#end
#implementation NSActionCell (DragCell)
- (void)setControlView:(NSView *)view
{
// this is a bit of a hack, but the easiest way to make the control dragging work.
// force the control to accept image drags.
// the control will forward us the drag destination events via our DragControl category
[view registerForDraggedTypes:[NSImage imagePasteboardTypes]];
[super setControlView:view];
}
- (BOOL)trackMouse:(NSEvent *)theEvent inRect:(NSRect)cellFrame ofView:(NSView *)controlView untilMouseUp:(BOOL)untilMouseUp
{
BOOL result = NO;
NSPoint currentPoint = theEvent.locationInWindow;
BOOL done = NO;
BOOL trackContinously = [self startTrackingAt:currentPoint inView:controlView];
BOOL mouseIsUp = NO;
NSEvent *event = nil;
while (!done)
{
NSPoint lastPoint = currentPoint;
event = [NSApp nextEventMatchingMask:(NSLeftMouseUpMask|NSLeftMouseDraggedMask)
untilDate:[NSDate distantFuture]
inMode:NSEventTrackingRunLoopMode
dequeue:YES];
if (event)
{
currentPoint = event.locationInWindow;
// Send continueTracking.../stopTracking...
if (trackContinously)
{
if (![self continueTracking:lastPoint
at:currentPoint
inView:controlView])
{
done = YES;
[self stopTracking:lastPoint
at:currentPoint
inView:controlView
mouseIsUp:mouseIsUp];
}
if (self.isContinuous)
{
[NSApp sendAction:self.action
to:self.target
from:controlView];
}
}
mouseIsUp = (event.type == NSLeftMouseUp);
done = done || mouseIsUp;
if (untilMouseUp)
{
result = mouseIsUp;
} else {
// Check if the mouse left our cell rect
result = NSPointInRect([controlView
convertPoint:currentPoint
fromView:nil], cellFrame);
if (!result)
done = YES;
}
if (done && result && ![self isContinuous])
[NSApp sendAction:self.action
to:self.target
from:controlView];
else {
done = YES;
result = YES;
// this bit-o-magic executes on either a drag event or immidiately following timer expiration
// this initiates the control drag event using NSDragging protocols
NSControl* cv = (NSControl*)self.controlView;
NSDraggingSession* session = [cv beginDraggingSessionWithDraggingCell:self
event:theEvent];
// Note that you will get an ugly flash effect when the image returns if this is set to yes
// you can work around it by setting NO and faking the release by animating an NSWindowSubclass with the image as the content
// create the window in the drag ended method for NSDragOperationNone
// there is [probably a better and easier way around this behavior by playing with view animation properties.
session.animatesToStartingPositionsOnCancelOrFail = YES;
}
}
}
return result;
}
#pragma mark - NSDraggingSource Methods
- (NSDragOperation)draggingSession:(NSDraggingSession *)session sourceOperationMaskForDraggingContext:(NSDraggingContext)context
{
switch(context) {
case NSDraggingContextOutsideApplication:
return NSDragOperationNone;
break;
case NSDraggingContextWithinApplication:
default:
return NSDragOperationPrivate;
break;
}
}
- (void)draggingSession:(NSDraggingSession *)session endedAtPoint:(NSPoint)screenPoint operation:(NSDragOperation)operation
{
// now tell the control view the drag ended so it can do any cleanup it needs
// this is somewhat hackish
[self.controlView draggingEnded:nil];
}
#end
There must be a layer enabled somewhere in your view hierarchy, otherwise there wouldn't be a fade animation. Here is my way of disabling such animations:
#interface NoAnimationImageView : NSImageView
#end
#implementation NoAnimationImageView
+ (id)defaultAnimationForKey: (NSString *)key
{
return nil;
}
#end
The solution you already tried by setting the view animations dictionary should work. But not for the keys you mention but for the following. Use it somewhere before the animation is triggered the first time. If you have to do it on the window or view or both, I don't know.
NSMutableDictionary *animations = [NSMutableDictionary dictionaryWithDictionary:[[theViewOrTheWindow animator] animations];
[animations setObject:[NSNull null] forKey: NSAnimationTriggerOrderIn];
[animations setObject:[NSNull null] forKey: NSAnimationTriggerOrderOut];
[[theViewOrTheWindow animator] setAnimations:animations];
Or also just remove the keys if they are there (might not be the case as they are implicit / default):
NSMutableDictionary *animations = [NSMutableDictionary dictionaryWithDictionary:[[theViewOrTheWindow animator] animations];
[animations removeObjectForKey:NSAnimationTriggerOrderIn];
[animations removeObjectForKey:NSAnimationTriggerOrderOut];
[[theViewOrTheWindow animator] setAnimations:animations];
Ok. I figured out that the animation I'm seeing is not the control, the superview, nor the control's window. It appears that animatesToStartingPositionsOnCancelOrFail causes NSDraggingSession to create a window (observed with QuartzDebug) and put the drag image in it and it is this window that animates back to the origin and fades out before the setHidden: call is executed (i.e. before the drag operation is concluded).
Unfortunately, the window that it creates is not an NSWindow so creating a category on NSWindow doesn't disable the fade animation.
Secondly, there is no public way that I know of to get a handle on the window, so I can't attempt directly manipulating the window instance.
It looks like maybe my workaround is the best way to do this, after all its not far from what AppKit does for you anyway.
If anybody knows how to get a handle on this window, or what class it is I would be interested to know.
I'm using EGORefreshTableHeader in conjunction with a static cell (defined in a nib) in a UITableView.
I'd like to be able to scroll the tableview down to trigger the EGORefreshTableHeader and thus refresh the data in the static cell. BUT, as the static cell is sized to fit perfectly in the window, I want to prevent folks from being able to scroll up.
Is there a way to do this?
Try this:
CGFloat a;
-(void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{
a = scrollView.contentOffset.y;
}
-(void)scrollViewDidScroll:(UIScrollView *)scrollView
{
if (scrollView.contentOffset.y > a)
{
[scrollView setScrollEnabled:NO];
[scrollView setContentOffset:CGPointMake(0, a)];
}
[scrollView setScrollEnabled:YES];
}
I'm using a textured window that has a tab bar along the top of it, just below the title bar.
I've used -setContentBorderThickness:forEdge: on the window to make the gradient look right, and to make sure sheets slide out from the right position.
What's not working however, is dragging the window around. It works if I click and drag the area that's actually the title bar, but since the title bar gradient spills into a (potentially/often empty) tab bar, it's really easy to click too low and it feels really frustrating when you try to drag and realise the window is not moving.
I notice NSToolbar, while occupying roughly the same amount of space below the title bar, allows the window to be dragged around when the cursor is over it. How does one implement this?
Thanks.
I tried the mouseDownCanMoveWindow solution (https://stackoverflow.com/a/4564146/901641) but it didn't work for me. I got rid of that method and instead added this to my window subclass:
- (BOOL)isMovableByWindowBackground {
return YES;
}
which worked like a charm.
I found this here:
-(void)mouseDown:(NSEvent *)theEvent {
NSRect windowFrame = [[self window] frame];
initialLocation = [NSEvent mouseLocation];
initialLocation.x -= windowFrame.origin.x;
initialLocation.y -= windowFrame.origin.y;
}
- (void)mouseDragged:(NSEvent *)theEvent {
NSPoint currentLocation;
NSPoint newOrigin;
NSRect screenFrame = [[NSScreen mainScreen] frame];
NSRect windowFrame = [self frame];
currentLocation = [NSEvent mouseLocation];
newOrigin.x = currentLocation.x - initialLocation.x;
newOrigin.y = currentLocation.y - initialLocation.y;
// Don't let window get dragged up under the menu bar
if( (newOrigin.y+windowFrame.size.height) > (screenFrame.origin.y+screenFrame.size.height) ){
newOrigin.y=screenFrame.origin.y + (screenFrame.size.height-windowFrame.size.height);
}
//go ahead and move the window to the new location
[[self window] setFrameOrigin:newOrigin];
}
It works fine, though I'm not 100% sure I'm doing it correctly. There's one bug I've found so far, and that's if the drag begins inside a subview (a tab itself) and then enters the superview (the tab bar). The window jumps around. Some -hitTest: magic, or possibly even just invalidating initialLocation on mouseUp should probably fix that.
As of macOS 10.11, the simplest way to do this is to utilize the new -[NSWindow performWindowDragWithEvent:] method:
#interface MyView () {
BOOL movingWindow;
}
#end
#implementation MyView
...
- (BOOL)mouseDownCanMoveWindow
{
return NO;
}
- (void)mouseDown:(NSEvent *)event
{
movingWindow = NO;
CGPoint point = [self convertPoint:event.locationInWindow
fromView:nil];
// The area in your view where you want the window to move:
CGRect movableRect = CGRectMake(0, 0, 100, 100);
if (self.window.movableByWindowBackground &&
CGRectContainsPoint(movableRect, point)) {
[self.window performWindowDragWithEvent:event];
movingWindow = YES;
return;
}
// Handle the -mouseDown: as usual
}
- (void)mouseDragged:(NSEvent *)event
{
if (movingWindow) return;
// Handle the -mouseDragged: as usual
}
#end
Here, -performWindowDragWithEvent: will handle the correct behavior of not overlapping the menu bar, and will also snap to edges on macOS 10.12 and later. Be sure to include a BOOL movingWindow instance variable with your view's private interface so you can avoid -mouseDragged: events once you determined you don't want to process them.
Here, we are also checking that -[NSWindow movableByWindowBackground] is set to YES so that this view can be used in non-movable-by-window-background windows, but that is optional.
Have you tried overriding the NSView method mouseDownCanMoveWindow to return YES?
It works for me after TWO steps:
Subclass NSView, override the mouseDownCanMoveWindow to return YES.
Subclass NSWindow, override the isMovableByWindowBackground to return YES.
It's quite easy:
override mouseDownCanMoveWindow property
override var mouseDownCanMoveWindow:Bool {
return false
}
If you got a NSTableView in your window, with selection enabled, overriding the mouseDownCanMoveWindow property won't work.
You need instead to create a NSTableView subclass and override the following mouse events (and use the performWindowDragWithEvent: mentioned in Dimitri answer):
#interface WindowDraggableTableView : NSTableView
#end
#implementation WindowDraggableTableView
{
BOOL _draggingWindow;
NSEvent *_mouseDownEvent;
}
- (void)mouseDown:(NSEvent *)event
{
if (self.window.movableByWindowBackground == NO) {
[super mouseDown:event]; // Normal behavior.
return;
}
_draggingWindow = NO;
_mouseDownEvent = event;
}
- (void)mouseDragged:(NSEvent *)event
{
if (self.window.movableByWindowBackground == NO) {
[super mouseDragged:event]; // Normal behavior.
return;
}
assert(_mouseDownEvent);
_draggingWindow = YES;
[self.window performWindowDragWithEvent:_mouseDownEvent];
}
- (void)mouseUp:(NSEvent *)event
{
if (self.window.movableByWindowBackground == NO) {
[super mouseUp:event]; // Normal behavior.
return;
}
if (_draggingWindow == YES) {
_draggingWindow = NO;
return; // Event already handled by `performWindowDragWithEvent`.
}
// Triggers regular table selection.
NSPoint locationInWindow = event.locationInWindow;
NSPoint locationInTable = [self convertPoint:locationInWindow fromView:nil];
NSInteger row = [self rowAtPoint:locationInTable];
if (row >= 0 && [self.delegate tableView:self shouldSelectRow:row])
{
NSIndexSet *rowIndex = [NSIndexSet indexSetWithIndex:row];
[self selectRowIndexes:rowIndex byExtendingSelection:NO];
}
}
#end
Also don't forget to set the corresponding window movableByWindowBackground property as well:
self.window.movableByWindowBackground = YES;
When you set property isMovableByWindowBackground in viewDidLoad, it may not work because the window property of the view is not yet set. In that case, try this:
override func viewDidAppear() {
self.view.window?.isMovableByWindowBackground = true
}
Thank you! Dimitris' answer solved my issue but I needed it in swift 5. Here is what I came up with.
final class BlahField: NSTextView {
var movingWindow = false
override func mouseDown(with event: NSEvent) {
movingWindow = false
let point = self.convert(event.locationInWindow, from: nil)
if (self.window!.isMovableByWindowBackground && self.frame.contains(point)) {
self.window?.performDrag(with: event)
movingWindow = true
return
}
}
override func mouseDragged(with event: NSEvent) {
if (movingWindow) {
return
}
}
}