NSTextView auto-scroll - objective-c

Suppose there is a window with has a NSTextView which contains enough text to trigger the scrollbars. When I resize the window, the textview is automatically scrolled so that the line which contains the cursor appears in the middle of the textview.
For example, this can also be seen in TextEdit in MacOS: paste bunch of text in it, scroll almost to the top [1], place cursor into the first visible line and resize the window. Now the view should scroll its content so that the cursor lands in the middle of the view.
My question is, how do I turn off this behavior? That is, I would like the textview to never automatically scroll the cursor to the middle when the window gets resized..?
[1] The actual scroll position at which the said behavior happens may require some trial-and-error, as I was unable to find out a pattern at which this happens. In my testing it happened when the scrollbar is at 10% - 30% position of the total height (from the top).

You can do the tweak like this below:-
Create Custom Class of NSTextView and implement one delegate method for textview resizing and one method when click on textview. Refer below:-
.h file
#interface textView : NSTextView
#end
.m file
#import "textView.h"
#implementation textView
- (void)drawRect:(NSRect)dirtyRect {
[super drawRect:dirtyRect];
// Drawing code here.
}
//Below delegate method which will call when resize the textview. So just set your text view to be non editable.
- (void)viewDidEndLiveResize
{
[self setEditable:NO];
[self setSelectable:NO];
}
//Now when you click on the textview below method will called.
-(void)mouseDown:(NSEvent*) theEvent
{
[super mouseDown:theEvent];
[self setEditable:YES];
[self setSelectable:YES];
}
-(void)keyDown:(NSEvent *)theEvent
{
[super keyDown:theEvent];
[self setEditable:YES];
[self setSelectable:YES];
}
#end
Edit:-
Also, mention the custom class name in interface builder inside textview -> Custom Class

Related

Move NSWindow by dragging NSView that is above other views

I have a macOS application that contains an NSTableView and an NSVisualEffectView. The visual effect view is acting like a bar at the bottom of the window, it is in the table view (containing a few buttons/etc..).
Anyway if I want to move the NSWindow by dragging the visual effect view, it will only work if the table view is not below the visual effect view. The reason I want visual effect view to be above the table view is so that I get a nice blur effect when then the user is scrolling through the table view content.
However, when the visual effect view is above the table view, the mouse/drag/etc events are not registered. Instead, they get passed to the table view. How can I stop this from happening?
I tried subclassing NSVisualEffectView, but everything I have tried has failed. Here is my code:
#import <Cocoa/Cocoa.h>
#interface BottomMainBar : NSVisualEffectView {
}
#end
Here is the implementation code:
#import "BottomMainBar.h"
#implementation BottomMainBar
/// DRAW RECT METHOD ///
-(void)drawRect:(NSRect)dirtyRect {
[super drawRect:dirtyRect];
[self setWantsLayer:YES];
[self.window setMovableByWindowBackground:YES];
[self setAcceptsTouchEvents:YES];
[self registeredDraggedTypes];
}
/// OTHER METHODS ///
-(BOOL)mouseDownCanMoveWindow {
return YES;
}
-(BOOL)acceptsFirstMouse:(NSEvent *)event {
return YES;
}
-(void)mouseDown:(NSEvent *)event {}
-(void)mouseDragged:(NSEvent *)event {}
-(void)mouseUp:(NSEvent *)event {}
-(void)mouseEntered:(NSEvent *)event {}
-(void)mouseExited:(NSEvent *)event {}
#end
Nothing I have tried has worked, how can I stop the visual effect view from passing on the mouse events to the layer below it?
Thanks for your time, Dan.
In the end I managed to find out a solution and thankfully it involves NO libraries or open source code (and obviously no private apis).
The problem
I have a NSVisualEffectView that spans the width of my view controller and it 38 px tall. It is positioned at the top of my view controller. It acts as a custom toolbar that contains a few buttons and labels. It is placed above a NSTableView that displays all sorts of content (images, video, text, etc...).
I placed the visual effect view above the table view, because I wanted to have a nice blur effect when the user scrolled the table view. The problem with this, is that the mouse down events on the visual effect view, get passed to table view and NOT the overall NSWindow. This results in the user being unable to drag and move the NSWindow, when they click and drag the visual effect view (because the mouse down events are not passed to the window).
I noticed that the top 10px of the visual effect DID pass the mouse down events to the window and not the table view. This is because the window's title bar is around 10-15px tall. However my visual effect view is 38px tall, so the bottom half of my visual effect view was unable to move the window.
The solution
The solution involves making two subclasses, one for the visual effect view and another for the NSWindow. The subclass for the visual effect view, simply passes the mouse down events to the nextResponder (which can be the table view or the window - depending on the size of the window title bar).
Header code (Visual Effect View class):
#import <Cocoa/Cocoa.h>
#interface TopMainBar : NSVisualEffectView {
}
#end
Implementation code (Visual Effect View class):
#import "TopMainBar.h"
#implementation TopMainBar
/// INIT WITH FRAME ///
-(id)initWithFrame:(NSRect)frameRect {
if ((self = [super initWithFrame:frameRect])) {
[self setWantsLayer:YES];
[self.window setMovableByWindowBackground:YES];
}
return self;
}
/// MOUSE METHODS ///
-(void)mouseDown:(NSEvent *)event {
[self.window mouseDown:event];
}
#end
The subclass for the window involves turning the window title bar into a toolbar, this in effect increases the size of the title bar (and as it happens increases it to around 38 px which is exactly what I needed). The ideal solution, would involve being able to increase the title bar height to any custom size, however that is not possible, so the toolbar solution is the only way.
Because the size of the title bar is increased, all the mouse down events are not passed to the window and not the table view. This enables the user to drag the window from any part of the visual effect view.
Header code (Window class):
#import <Cocoa/Cocoa.h>
#interface CustomWindow : NSWindowController <NSWindowDelegate> {
}
// UI methods.
-(BOOL)isWindowFullScreen;
#end
Implementation code (Window class):
#import "CustomWindow.h"
#interface CustomWindow ()
#end
#implementation CustomWindow
/// WINDOW DID LOAD ///
-(void)windowDidLoad {
[super windowDidLoad];
// Ensure this window is the current selected one.
[self.window makeKeyAndOrderFront:self];
// Ensure the window can be moved.
[self.window setMovableByWindowBackground:YES];
// Set the window title bar options.
self.window.titleVisibility = NSWindowTitleHidden;
self.window.titlebarAppearsTransparent = YES;
self.window.styleMask |= (NSWindowStyleMaskFullSizeContentView | NSWindowStyleMaskUnifiedTitleAndToolbar | NSWindowStyleMaskTitled);
self.window.movableByWindowBackground = YES;
self.window.toolbar.showsBaselineSeparator = NO;
self.window.toolbar.fullScreenAccessoryView.hidden = YES;
self.window.toolbar.visible = ![self isWindowFullScreen];
}
/// UI METHODS ///
-(BOOL)isWindowFullScreen {
return (([self.window styleMask] & NSWindowStyleMaskFullScreen) == NSWindowStyleMaskFullScreen);
}
/// WINDOW METHODS ///
-(void)windowWillEnterFullScreen:(NSNotification *)notification {
self.window.toolbar.visible = NO;
}
-(void)windowDidEnterFullScreen:(NSNotification *)notification {
self.window.toolbar.visible = NO;
}
-(void)windowWillExitFullScreen:(NSNotification *)notification {
self.window.toolbar.visible = YES;
}
-(void)windowDidExitFullScreen:(NSNotification *)notification {
self.window.toolbar.visible = YES;
}
/// OTHER METHODS ///
-(BOOL)mouseDownCanMoveWindow {
return YES;
}
#end
In the custom window class you can see that I am changing the toolbar visibility depending on the full screen state of the window. This is to stop title bar appearing and covering my custom visual effect view up, when the window goes into full screen mode.
In order for this to work, you need to add an empty toolbar to your window, you can do this in interface builder, by dragging and dropping a NSToolbar object, to your window.
Make sure you connect the window to the window delegate, otherwise the full screen delegate method will not be called.
Conclusion
This solution involves increasing the size of the title bar by changing it into a toolbar. The mouse down events that are passed from the visual effect view class, are then read by the window (not any other view behind it) and thus the window can be moved.

NSTextView placeholder text only visible when selected

I'm currently experiencing a problem like the one described in this SO question (which does not currently have an accepted answer), in that my text fields' placeholder text is not visible unless the text field is selected.
I have subclassed NSTextField (code below):
#interface CustomTextField : NSTextField
#property (nonatomic, strong) IBInspectable NSImage *backgroundImage;
#end
#implementation CustomTextField
- (void)awakeFromNib
{
[self setDrawsBackground:NO];
}
- (void)drawRect:(NSRect)rect
{
NSImage *backgroundImage = self.backgroundImage;
[super drawRect:rect];
[self lockFocus];
[backgroundImage drawInRect:rect fromRect:rect operation:NSCompositeSourceOver fraction:1.0];
[self unlockFocus];
}
#end
I've set the class of my text field in Interface Builder to be CustomTextField, and set the placeholder text as shown below:
As can be seen in the screenshots below, the placeholder text is only visible if the text field is selected...
Text field one:
Text field two:
Does anyone have any idea on how I can make the placeholder text visible regardless of if the user has selected it? Thank you!
You are drawing your background image after the superclass has drawn its content. So, you are probably drawing over whatever the superclass has drawn, replacing it.
You should presumably draw your background image first, before calling through to super. Also, you shouldn't lock focus (or unlock it) in -drawRect:. The frameworks have already done that for you.
The reason why the placeholder shows up when your text field has focus is that you're actually seeing the field editor, not the text field at that point. The field editor is an instance of NSTextView ("view", not "field") that's inserted into the view hierarchy on top of the text field to handle text editing duties. So, when the text field has focus, your custom class's drawing it irrelevant.

NSTextView not properly resizing with auto layout

I have a simple layout, which consists of NSView and its subview NSTextView. NSTextView is programmatically filled with some text that spawns multiple lines. I tie everything together using auto-layout (all done programmatically). However, when everything is displayed NSTextView is cut off, only one line is showing.
After searching the web, the best answer I could find was:
Using Autolayout with expanding NSTextViews
However, this only works if I manually change the text in NSTextView after everything is displayed (which is not really my use case). The views are readjusted and the whole NSTextView is displayed.
I am trying to figure out when NSViewController is done with laying out subviews so that I could call invalidateIntrinsicContentSize on the NSTextView. The equivalent of viewDidLayoutSubviews in UIViewController.
Nothing I tried worked so far. I attempted calling invalidateIntrinsicContentSize for NSTextView:
At the end of loadView
After I filled NSTextView with my text
Is there a better way to achieve this?
After further research, found the answer:
Create custom NSView subclass that contains NSTextView
In NSView subclass override layout method that calls invalidateIntrinsicContentSize
Also check out this link that explains subtleties of auto layout and intrinsic content size (among many other things):
http://www.objc.io/issue-3/advanced-auto-layout-toolbox.html
Sample code:
#interface MyView : NSView
#property MyTextView *textView;
#end
#implementation MyView
// init & create content & set constraints
-(void) layout {
[super layout];
[self.textView invalidateIntrinsicContentSize];
}
#end
Implementation of MyTextView:
#implementation MyTextView
- (NSSize) intrinsicContentSize {
NSTextContainer* textContainer = [self textContainer];
NSLayoutManager* layoutManager = [self layoutManager];
[layoutManager ensureLayoutForTextContainer: textContainer];
return [layoutManager usedRectForTextContainer: textContainer].size;
}
- (void) didChangeText {
[super didChangeText];
[self invalidateIntrinsicContentSize];
}
#end

UIVIew endEditing:YES doesnt hide the keyboard

I have a UIVIew which is a subview and it contains several UITextFields. One of these textfields (which is for DATE) should not be editable using the keyboard, instead of this I use a PopOver with a Datepicker inside.
I run a method when the UIControlEventEditingDidBegin is reached. This method calls the resignFirstResponder on the DateTextField.
Everything works fine if the DateTextField is the first field to edit, but when another textField is edited and of course shows the keyboard and then try to edit the DateField, the keyboard doesn't hide and everything goes normal but with the Keyboard doing anything.
I have tried to call the method endEditing:YES before the resignFirstResponder but it doesn't work. I have tried to run the endEditing:YES and resignFirstResponder on the didEndEditing text field method but theres no way to get that keyboard out.
here is my method:
- (void)showDatePopOver:(id)sender{
[self.view endEditing:YES];
UITextField *textField = (UITextField *)sender;
[sender resignFirstResponder]; // hide keyboard
/** POP OVER LINES**/
}
You should use the textFieldShouldBeginEditing: delegate method instead of resigning first responder in didBeginEditing:
This will allow editing on ALL BUT the dateTextField text field:
- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField {
return (![textField isEqual:dateTextField]);
}
You should specify that your view controller is a text view delegate as well like so (in the interface declaration [.h file]):
#interface MyViewController : UIViewController <UITextFieldDelegate>

Bring to front DatePicker on an UITextField

When an UITextField is firstResponder, I would like to bring to front an UIDatePicker (bottom part of the screen) without the "going down keyboard" (no call to UITextField resignFirstResponder).
The aim is to process like UIKeyboard of UITextField which pop-up on nearly everything when it becomeFirstResponder. modalViewController seems to be fullscreen only.
- showDatePicker:(id)sender {
if([taskName isFirstResponder]) [taskName resignFirstResponder];
[self.view.window addSubview: self.pickerView];
// size up the picker view and compute the start/end frame origin
(...)
[UIView commitAnimations];
}
This example is an animation of keyboard going down, and DatePicker going up, behind and not in front.
Do you know a solution ? A piece of code would be welcome. Thanks in advance.
This is simply done by setting the input view of the text field to the Picker View. Then, on Editing did begin tell the picker view to becomeFirst responder. Like this
textField.inputView = pickerView
then using an IBAction called when the Editing Did Begin
-(IBAction) setPickerViewAsFirstResponder:(id)sender
{
[pickerView becomeFirstResponder];
}
This works perfectly. You'll need to implement code to actually set what the picker view is currently showing to be a string in the text field still.
This definitely can be done... simply implement the method below after setting UIViewController <UITextFieldDelegate> in your .h
Long story short, this overrides the keyboard loading before text editing begins.
- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField {
// Make a new view, or do what you want here
UIDatePicker *pv = [[UIDatePicker alloc] initWithFrame:CGRectMake(0,245,0,0)];
[self.view addSubview:pv];
return NO;
}