I have an NSScrollView that contains a Drawing. Drawing is a subclass of NSView and is a drop target. I have another class called dragBox which is an NSBox and a drag source. I want to be able to dynamically change the size of the Drawing to accommodate dragging the dragBox outside the size of the NSScrollView. Currently I define a "hot area" in the NSScrollView's content view that automatically scrolls. The problem is that when I drop the dragBox, it doesn't drop it in the virtual space that was just created. It drops it relative to the size of the viewport. Here's the code.
#implementation Drawing
-(NSDragOperation)draggingUpdated:(id <NSDraggingInfo>)sender // validate
{
NSLog(#"Updated");
_drawHintPoint = [sender draggedImageLocation];
if ([sender draggingLocation].x > [[self superview] bounds].size.width - 30)
{
NSRect scrollRect = NSMakeRect(0,0, [self bounds].size.width + 30, [self bounds].size.height) ;
[self setFrame:scrollRect];
_drawHintPoint = NSMakePoint([self bounds].size.width + 30, [sender draggingLocation].y);
[self scrollPoint:_drawHintPoint];
}
return NSDragOperationEvery;
}
-(BOOL) prepareForDragOperation:(id<NSDraggingInfo>)sender
{
NSLog(#"Prepared");
return YES;
}
- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender {
NSLog(#"Move Box");
NSPoint pt = _drawHintPoint;
pt.x = pt.x - 32;
pt.y = pt.y - 32;
[[_ico box] setFrameOrigin:pt];
return YES;
}
- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender {
NSLog(#"Drag Entered");
_drawHintPoint = NSMakePoint(0, 0);
return NSDragOperationEvery;
}
How do I drop the dragBox so that it drops it in the virtual space provided?
Bruce
Solved!
In draggingUpdated, this did the trick:
_drawHintPoint = [[self superview] convertPoint:[sender draggingLocation] fromView:nil];
You need to convert the dragging location to coordinates in the scroll view. [self superview] in the above line refers to the NSClipRect.
Related
So I have a borderless window with it's background color set to clear. Inside it, I have an NSView with some custom drawing. It looks like this:
https://www.dropbox.com/s/16dh9ez3z04eic7/Screenshot%202016-02-10%2012.15.35.png?dl=0
The problem is, the custom drawing is casting a shadow, which you can see if the view is hidden:
https://www.dropbox.com/s/04ymp0b2yqi5egu/Screenshot%202016-02-10%2012.19.04.png?dl=0
I only want the shadow around the frame of the window! How can I achieve this?
NOTE: strangely enough, giving the window a title bar provides the behavior I want: https://www.dropbox.com/s/1vv9iwb5403tufe/Screenshot%202016-02-10%2012.25.29.png?dl=0 - Unfortunately, I don't want a title bar.
The code:
#implementation MyWindow
/*
In Interface Builder, the class for the window is set to this subclass. Overriding the initializer
provides a mechanism for controlling how objects of this class are created.
*/
- (id)initWithContentRect:(NSRect)contentRect
styleMask:(NSUInteger)aStyle
backing:(NSBackingStoreType)bufferingType
defer:(BOOL)flag {
self = [super initWithContentRect:contentRect styleMask:NSBorderlessWindowMask backing:bufferingType defer:NO];
if (self != nil) {
[self setOpaque:NO];
[self setBackgroundColor:[NSColor clearColor]];
[self makeKeyAndOrderFront:NSApp];
[self setMovable:YES];
[self setShowsResizeIndicator:YES];
[self setResizeIncrements:NSMakeSize(2, 2)];
[self setLevel:TOP_LEVEL];
}
return self;
}
/*
Custom windows that use the NSBorderlessWindowMask can't become key by default. Override this method
so that controls in this window will be enabled.
*/
- (BOOL)canBecomeKeyWindow {
return YES;
}
#end
And the ContentView:
#implementation WindowContentView
- (id)initWithFrame:(NSRect)frame {
self = [super initWithFrame:frame];
if (self) {
// Initialization code here.
}
return self;
}
- (void)drawRect:(NSRect)dirtyRect {
// we need to redraw the whole frame
dirtyRect = self.bounds;
// background
NSBezierPath *framePath = [NSBezierPath bezierPathWithRoundedRect:dirtyRect xRadius:0.0f yRadius:0.0f];
[[NSColor colorWithCalibratedRed:0.0f/255.0f green:255.0f/255.0f blue:153.0f/255.0f alpha:0.3f] setFill];
[framePath fill];
// border
[[NSColor colorWithCalibratedRed:0.0f/255.0f green:255.0f/255.0f blue:153.0f/255.0f alpha:1.0f] setStroke];
framePath.lineWidth = 1.0f;
[framePath stroke];
// grid
[self drawGridInRect:dirtyRect];
[super drawRect:dirtyRect];
}
- (void)drawGridInRect:(NSRect)rect {
int rows = 5;
int columns = 5;
float gridWidth = CGRectGetWidth(rect)/columns;
float gridHeight = CGRectGetHeight(rect)/rows;
[[NSColor colorWithCalibratedRed:0.0f/255.0f green:255.0f/255.0f blue:153.0f/255.0f alpha:0.2f] setStroke];
[NSBezierPath setDefaultLineWidth:1.0f];
for(int i=1; i<rows; i++) {
[NSBezierPath strokeLineFromPoint:NSMakePoint(gridWidth*i, CGRectGetMaxY(rect))
toPoint:NSMakePoint(gridWidth*i, CGRectGetMinY(rect))];
}
for(int i=1; i<columns; i++) {
[NSBezierPath strokeLineFromPoint:NSMakePoint(CGRectGetMaxX(rect), gridHeight*i)
toPoint:NSMakePoint(CGRectGetMinX(rect), gridHeight*i)];
}
}
#end
I'm making a little server app for OS X and I'm using an NSTextView to log some info about connected clients.
Whenever I need to log something I'm appending the new message to the text of the NSTextView this way:
- (void)logMessage:(NSString *)message
{
if (message) {
self.textView.string = [self.textView.string stringByAppendingFormat:#"%#\n",message];
}
}
After this I'd like the NSTextField (or maybe I should say the NSClipView that contains it) to scroll down to show the last line of its text (obviously it should scroll only if the last line is not visible yet, in fact if then new line is the first line I log it is already on the screen so there is no need to scroll down).
How can I do that programmatically?
Found solution:
- (void)logMessage:(NSString *)message
{
if (message) {
[self appendMessage:message];
}
}
- (void)appendMessage:(NSString *)message
{
NSString *messageWithNewLine = [message stringByAppendingString:#"\n"];
// Smart Scrolling
BOOL scroll = (NSMaxY(self.textView.visibleRect) == NSMaxY(self.textView.bounds));
// Append string to textview
[self.textView.textStorage appendAttributedString:[[NSAttributedString alloc]initWithString:messageWithNewLine]];
if (scroll) // Scroll to end of the textview contents
[self.textView scrollRangeToVisible: NSMakeRange(self.textView.string.length, 0)];
}
As of OS 10.6 it's as simple as nsTextView.scrollToEndOfDocument(self).
Swift 4 + 5
let smartScroll = self.textView.visibleRect.maxY == self.textView.bounds.maxY
self.textView.textStorage?.append("new text")
if smartScroll{
self.textView.scrollToEndOfDocument(self)
}
I've been messing with this for a while, because I couldn't get it to work reliably. I've finally gotten my code working, so I'd like to post it as a reply.
My solution allows you to scroll manually, while output is being added to the view. As soon as you scroll to the absolute bottom of the NSTextView, the automatic scrolling will resume (if enabled, that is).
First a category to #import this only when needed...
FSScrollToBottomExtensions.h:
#interface NSView (FSScrollToBottomExtensions)
- (float)distanceToBottom;
- (BOOL)isAtBottom;
- (void)scrollToBottom;
#end
FSScrollToBottomExtensions.m:
#implementation NSView (FSScrollToBottomExtensions)
- (float)distanceToBottom
{
NSRect visRect;
NSRect boundsRect;
visRect = [self visibleRect];
boundsRect = [self bounds];
return(NSMaxY(visRect) - NSMaxY(boundsRect));
}
// Apple's suggestion did not work for me.
- (BOOL)isAtBottom
{
return([self distanceToBottom] == 0.0);
}
// The scrollToBottom method provided by Apple seems unreliable, so I wrote this one
- (void)scrollToBottom
{
NSPoint pt;
id scrollView;
id clipView;
pt.x = 0;
pt.y = 100000000000.0;
scrollView = [self enclosingScrollView];
clipView = [scrollView contentView];
pt = [clipView constrainScrollPoint:pt];
[clipView scrollToPoint:pt];
[scrollView reflectScrolledClipView:clipView];
}
#end
... create yourself an "OutputView", which is a subclass of NSTextView:
FSOutputView.h:
#interface FSOutputView : NSTextView
{
BOOL scrollToBottomPending;
}
FSOutputView.m:
#implementation FSOutputView
- (id)setup
{
...
return(self);
}
- (id)initWithCoder:(NSCoder *)aCoder
{
return([[super initWithCoder:aCoder] setup]);
}
- (id)initWithFrame:(NSRect)aFrame textContainer:(NSTextContainer *)aTextContainer
{
return([[super initWithFrame:aFrame textContainer:aTextContainer] setup]);
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
[super dealloc];
}
- (void)awakeFromNib
{
NSNotificationCenter *notificationCenter;
NSView *view;
// viewBoundsDidChange catches scrolling that happens when the caret
// moves, and scrolling caused by pressing the scrollbar arrows.
view = [self superview];
[notificationCenter addObserver:self
selector:#selector(viewBoundsDidChangeNotification:)
name:NSViewBoundsDidChangeNotification object:view];
[view setPostsBoundsChangedNotifications:YES];
// viewFrameDidChange catches scrolling that happens because text
// is inserted or deleted.
// it also catches situations, where window resizing causes changes.
[notificationCenter addObserver:self
selector:#selector(viewFrameDidChangeNotification:)
name:NSViewFrameDidChangeNotification object:self];
[self setPostsFrameChangedNotifications:YES];
}
- (void)handleScrollToBottom
{
if(scrollToBottomPending)
{
scrollToBottomPending = NO;
[self scrollToBottom];
}
}
- (void)viewBoundsDidChangeNotification:(NSNotification *)aNotification
{
[self handleScrollToBottom];
}
- (void)viewFrameDidChangeNotification:(NSNotification *)aNotification
{
[self handleScrollToBottom];
}
- (void)outputAttributedString:(NSAttributedString *)aAttributedString
flags:(int)aFlags
{
NSRange range;
BOOL wasAtBottom;
if(aAttributedString)
{
wasAtBottom = [self isAtBottom];
range = [self selectedRange];
if(aFlags & FSAppendString)
{
range = NSMakeRange([[self textStorage] length], 0);
}
if([self shouldChangeTextInRange:range
replacementString:[aAttributedString string]])
{
[[self textStorage] beginEditing];
[[self textStorage] replaceCharactersInRange:range
withAttributedString:aAttributedString];
[[self textStorage] endEditing];
}
range.location += [aAttributedString length];
range.length = 0;
if(!(aFlags & FSAppendString))
{
[self setSelectedRange:range];
}
if(wasAtBottom || (aFlags & FSForceScroll))
{
scrollToBottomPending = YES;
}
}
}
#end
... You can add a few more convenience methods to this class (I've stripped it down), so that you can output a formatted string.
- (void)outputString:(NSString *)aFormatString arguments:(va_list)aArguments attributeKey:(NSString *)aKey flags:(int)aFlags
{
NSMutableAttributedString *str;
str = [... generate attributed string from parameters ...];
[self outputAttributedString:str flags:aFlags];
}
- (void)outputLineWithFormat:(NSString *)aFormatString, ...
{
va_list args;
va_start(args, aFormatString);
[self outputString:aFormatString arguments:args attributeKey:NULL flags:FSAddNewLine];
va_end(args);
}
I have some customised NSTextView and custom input method so my option was to use:
self.scrollView.contentView.scroll(NSPoint(x: 1, y: self.textView.frame.size.height))
I have an app that resizes itself for different views using:
NSSize currentSize = [[box contentView] frame].size;
NSSize newSize = [v frame].size;
float deltaWidth = newSize.width - currentSize.width;
float deltaHeight = newSize.height - currentSize.height;
NSRect windowFrame = [w frame];
windowFrame.size.height += deltaHeight;
windowFrame.origin.y -= deltaHeight;
windowFrame.size.width += deltaWidth;
[box setContentView: nil];
[w setFrame: windowFrame
display: YES
animate: YES];
[box setContentView: v];
When you change the view, the window grows/shrinks based on the upper-left corner of the app. The app always starts on the first view, no matter which view the user was in when they quit, because this is a summary view. I also want it to be restorable so the last-used document is open on launch.
The problem: since most views are taller than the summary view, changing to one pushes the bottom left corner of the window farther down the screen. Now quit the app and relaunch, and the app positions the window to where that bottom left corner was previously. I know this is because that's where Cocoa puts the origin, but it makes more sense for the user to have the window restart with the same top left corner, otherwise the app is shifting down the screen each time it's opened.
I tried observing NSWindowWillCloseNotification and calling the above method again to reset the app to the summary view just before closing, but even though the code works, the window still starts in the wrong position - I'm guessing Cocoa sets an app's restorable defaults before the notification is sent.
It's been done before: System Preferences does it - it auto-resizes for views, dictates what your starting view is, but you can move the window and next time you open it, it'll be in the new position based on the top left corner. Anyone have an idea how to emulate that?
Edit: Document.xib has a small window with a popup button and a box. The methods below change the views and resize the window to fit those views. Everything works fine while the app is open, the window shifting is only an issue when the app is closed and re-opened with an active document (ie. using Lion's restore).
============================
| Jump to:___ | The "===" is the top and bottom edge of the window
| | The "___" is the pop-up menu for selecting the view
| -------------------------- | The dashed box on the inside is "box", which holds the views
|| ||
|| ||
|| ||
|| ||
|| ||
|| ||
| -------------------------- |
============================
- (void)windowControllerDidLoadNib:(NSWindowController *)aController
{
[super windowControllerDidLoadNib:aController];
//Note that init creates the array viewControllers
NSMenu *menu = [viewMenu menu];
NSUInteger i, itemCount;
itemCount = [viewControllers count];
for (i = 0; i < itemCount; i++) {
NSViewController *vc = [viewControllers objectAtIndex:i];
NSMenuItem *mi = [[NSMenuItem alloc] initWithTitle: [vc title] action: #selector(changeViewController:) keyEquivalent:#""];
[mi setTag: i];
[menu addItem: mi];
}
[self displayViewController: [viewControllers objectAtIndex: 0]];
[viewMenu selectItemAtIndex: 0];
}
- (void) displayViewController: (ManagingViewController *) vc {
//End editing
NSWindow *w = [box window];
BOOL ended = [w makeFirstResponder:w];
if (!ended) {
NSBeep();
return;
}
//Put view in box
NSView *v = [vc view];
NSSize currentSize = [[box contentView] frame].size;
NSSize newSize = [v frame].size;
float deltaWidth = newSize.width - currentSize.width;
float deltaHeight = newSize.height - currentSize.height;
NSRect windowFrame = [w frame];
windowFrame.size.height += deltaHeight;
windowFrame.origin.y -= deltaHeight;
windowFrame.size.width += deltaWidth;
[box setContentView: nil];
[w setFrame: windowFrame display: YES animate: YES];
[box setContentView: v];
}
- (IBAction)changeViewController:(id)sender {
NSUInteger i = [sender tag];
ManagingViewController *vc = [viewControllers objectAtIndex: i];
[self displayViewController: vc];
}
I tried putting the first block of suggested code from #trudyscousin just below the for loop in -windowControllerDidLoadNib: and that anchored the window correctly, but when the code reaches -displayViewController: it sets currentSize = (6,6). I'm not sure if that's because the box is empty in IB. It then adds the difference between (6,6) and the size of the SummaryView to the size of the window when the app last quit, making it huge.
I then tried using NSUserDefaults by adding two lines to -changeViewController:
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
[userDefaults setInteger: i forKey: #"lastViewController"];
and in -windowControllerDidLoadNib:
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
if ([userDefaults integerForKey: #"lastViewController"]) {
NSInteger i = [userDefaults integerForKey: #"lastViewController"];
ManagingViewController *vc = [viewControllers objectAtIndex: i];
NSView *v = [vc view];
NSSize currentSize = [v frame].size;
[box setFrameSize: currentSize];
[self displayViewController: vc];
[viewMenu selectItemAtIndex: i];
} else {
[self displayViewController: [viewControllers objectAtIndex: 0]];
[viewMenu selectItemAtIndex: 0];
}
This works, but forces the app to launch with the last used view, which wouldn't be so bad except that it will even do that when a new document is opened/created. If I get rid of the else (i.e., always run those last 2 lines), I'm back to where I started - the app launches being shifted vertically down the screen. It looks like you can't set the window's origin at launch, or maybe it reloads its own defaults at a later point.
The last thing I did was go back to my starting code (not using the mask code provided by #trudyscousin) but at the bottom of -windowControllerDidLoadNib: I added:
[self performSelector: #selector(adjustOrigin) withObject: nil afterDelay:0.0f];
which calls:
- (void) adjustOrigin {
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
if ([userDefaults integerForKey: #"lastViewController"]) {
NSInteger i = [userDefaults integerForKey: #"lastViewController"];
ManagingViewController *vc = [viewControllers objectAtIndex: i];
NSView *v = [vc view];
NSSize previousSize = [v frame].size;
NSSize currentSize = [[[viewControllers objectAtIndex: 0] view] frame].size;
CGFloat delta = previousSize.height - currentSize.height;
NSRect windowFrame = [[box window] frame];
windowFrame.origin.y += delta;
[[box window] setFrame: windowFrame display:YES];
}
}
That actually does work, but if multiple things are running, the lag is enough that you can see the window make the "jump" to its new origin, which is a little inelegant.
Your code appears to be sound, but the real problem may be your document xib.
If your document xib is set up for Auto Layout, turn that off. You can do that by selecting the file in Xcode. In the File inspector, turn off the "Use Auto Layout" check box.
Once you've done that, select your popup button. In the Size inspector, anchor the button at the top and left side. Select your box. In the Size inspector, anchor the box on all four sides, and make sure it expands horizontally and vertically.
To recap from my earlier answer, here's your -windowControllerDidLoadNib: method with the changes I had suggested earlier:
- (void)windowControllerDidLoadNib:(NSWindowController *)aController
{
[super windowControllerDidLoadNib:aController];
//Note that init creates the array viewControllers
NSMenu *menu = [viewMenu menu];
NSUInteger i, itemCount;
itemCount = [viewControllers count];
for (i = 0; i < itemCount; i++) {
NSViewController *vc = [viewControllers objectAtIndex:i];
NSMenuItem *mi = [[NSMenuItem alloc] initWithTitle: [vc title] action: #selector(changeViewController:) keyEquivalent:#""];
[mi setTag: i];
[menu addItem: mi];
}
// ---
NSWindow *myWindow = [[[self windowControllers] objectAtIndex:0] window];
NSUInteger styleMask = [myWindow styleMask];
styleMask ^= NSResizableWindowMask;
[myWindow setStyleMask:styleMask];
if ([myWindow setFrameUsingName:#"windowFrameAutosaveName"] == NO)
{
[myWindow center];
}
(void) [myWindow setFrameAutosaveName:#"windowFrameAutosaveName"];
styleMask &= ~NSResizableWindowMask;
[myWindow setStyleMask:styleMask];
// ---
[self displayViewController:[viewControllers objectAtIndex:0]];
[viewMenu selectItemAtIndex:0];
}
I removed the older comments but now indicate where the additional code is. With this, you shouldn't have to bother with maintaining your own defaults for the document, and you don't have to bother with adjusting the origin.
Put these two things together, and you (largely) have your fix.
Yes, I needed to qualify that somewhat. Yours is a document-based application, and so you're going to have to either come up with either a unique name under which to save your document's window location in your user defaults, or perhaps a means of somehow storing that information with the document itself.
As always, good luck to you in your endeavors.
I need to highlight line with caret in NSTextView using CALayer overlay and grab rect for line with this code in my subclassed NSTextView...:
- (NSRect)overlayRectForRange:(NSRange)aRange
{
NSScreen *currentScreen = [NSScreen currentScreenForMouseLocation];
NSRect rect = [self firstRectForCharacterRange:aRange];
rect = [self convertRectToLayer:rect];
rect.origin = [currentScreen flipPoint:rect.origin];
rect = [self.window convertRectToScreen:rect];
return rect;
}
...and put overlay:
- (void)focusOnLine
{
NSInteger caretLocation = [aTextView selectedRange].location;
NSRange neededRange;
(void)[layoutMgr lineFragmentRectForGlyphAtIndex:caretLocation effectiveRange:&neededRange];
CALayer *aLayer = [CALayer layer];
[aLayer setFrame:NSRectToCGRect([aTextView overlayRectForRange:neededRange])];
[aLayer setBackgroundColor:[[NSColor redColor] coreGraphicsColorWithAlfa:0.5]];
[[aTextView layer] addSublayer:aLayer];
}
As a result, the selection overlay coincides with width of desired line, but absolutely not matches by Y axis (X axis is ok).
What am I missing?
Okay, I completely rewrote method overlayRectForRange: and now all works fine. There is fixed code:
- (NSRect)overlayRectForRange:(NSRange)aRange
{
NSRange activeRange = [[self layoutManager] glyphRangeForCharacterRange:aRange actualCharacterRange:NULL];
NSRect neededRect = [[self layoutManager] boundingRectForGlyphRange:activeRange inTextContainer:[self textContainer]];
NSPoint containerOrigin = [self textContainerOrigin];
neededRect.origin.x += containerOrigin.x;
neededRect.origin.y += containerOrigin.y;
neededRect = [self convertRectToLayer:neededRect];
return neededRect;
}
#import "LoginScreen.h"
#define kTabBarHeight 1
#define kKeyboardAnimationDuration 0.3
#implementation LoginScreen
#synthesize userName,password,loginButton,scrollView;
BOOL keyboardIsShown;
// The designated initializer. Override if you create the controller programmatically and want to perform customization that is not appropriate for viewDidLoad.
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization.
}
return self;
}
// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad {
[super viewDidLoad];
// register for keyboard notifications
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardWillShow:)
name:UIKeyboardWillShowNotification
object:self.view.window];
// register for keyboard notifications
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardWillHide:)
name:UIKeyboardWillHideNotification
object:self.view.window];
keyboardIsShown = NO;
//make contentSize bigger than your scrollSize (you will need to figure out for your own use case)
// CGSize scrollContentSize = CGSizeMake(1024,700 );
// [scrollView setContentSize : scrollContentSize];
self.scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height)];
}
- (void)keyboardWillHide:(NSNotification *)n
{
NSDictionary* userInfo = [n userInfo];
// get the size of the keyboard
NSValue* boundsValue = [userInfo objectForKey:UIKeyboardBoundsUserInfoKey];
CGSize keyboardSize = [boundsValue CGRectValue].size;
// resize the scrollview
CGRect viewFrame = self.scrollView.frame;
// I'm also subtracting a constant kTabBarHeight because my UIScrollView was offset by the UITabBar so really only the portion of the keyboard that is leftover pass the UITabBar is obscuring my UIScrollView.
viewFrame.size.height += (keyboardSize.height - kTabBarHeight);
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationBeginsFromCurrentState:YES];
// The kKeyboardAnimationDuration I am using is 0.3
[UIView setAnimationDuration:kKeyboardAnimationDuration];
[self.scrollView setFrame:viewFrame];
[UIView commitAnimations];
keyboardIsShown = NO;
}
- (void)keyboardWillShow:(NSNotification *)n
{
// This is an ivar I'm using to ensure that we do not do the frame size adjustment on the UIScrollView if the keyboard is already shown. This can happen if the user, after fixing editing a UITextField, scrolls the resized UIScrollView to another UITextField and attempts to edit the next UITextField. If we were to resize the UIScrollView again, it would be disastrous. NOTE: The keyboard notification will fire even when the keyboard is already shown.
if (keyboardIsShown) {
return;
}
NSDictionary* userInfo = [n userInfo];
// get the size of the keyboard
NSValue* boundsValue = [userInfo objectForKey:UIKeyboardBoundsUserInfoKey];
CGSize keyboardSize = [boundsValue CGRectValue].size;
// resize the noteView
CGRect viewFrame = self.scrollView.frame;
// I'm also subtracting a constant kTabBarHeight because my UIScrollView was offset by the UITabBar so really only the portion of the keyboard that is leftover pass the UITabBar is obscuring my UIScrollView.
viewFrame.size.height -= (keyboardSize.height - kTabBarHeight);
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationBeginsFromCurrentState:YES];
// The kKeyboardAnimationDuration I am using is 0.3
[UIView setAnimationDuration:kKeyboardAnimationDuration];
[self.scrollView setFrame:viewFrame];
[UIView commitAnimations];
keyboardIsShown = YES;
}
- (IBAction) loginButton: (id) sender{
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
// Overriden to allow any orientation.
return YES;
}
- (void)didReceiveMemoryWarning {
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
// Release any cached data, images, etc. that aren't in use.
}
- (void)viewDidUnload {
[super viewDidUnload];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
// unregister for keyboard notifications while not visible.
[[NSNotificationCenter defaultCenter] removeObserver:self
name:UIKeyboardWillShowNotification
object:nil];
// unregister for keyboard notifications while not visible.
[[NSNotificationCenter defaultCenter] removeObserver:self
name:UIKeyboardWillHideNotification
object:nil];
}
- (void)dealloc {
[scrollView release];
[super dealloc];
}
#end
Can anybody tell me whats wrong in code below. The original view doesn't move up even though i am subtracting the keyboards height to the frame heights.
The scrollView doesn't move up when keyboard pops in? Am I missing some code here.
Well, the problem is that you're not telling your code where your text field is to begin with, so it has no idea where it needs to scroll from. You probably thought that your self.scrollView.frame does it, but that only tells the code the size of your ScrollView, not that it needs to scroll or should scroll. Here's what I did to get mine to work.
Look at the connections for one of your text fields. Drag and drop from the "Did Begin Editing" and "Did End Editing" into your .h file to create IBAction function declarations. Xcode is going to put functions into the .m, but replace them with these:
//even though these functions don't reference the IBAction that we placed for the
"DidBeginEditing" sender for a text field, it will still call these functions.
We need to let the code know what text field we just touched so we can go through
the functions that reset the view size if the text field is under the keyboard.
- (void)textFieldDidBeginEditing:(UITextField *)textField
{
currentTextField = textField;
}
- (void)textFieldDidEndEditing:(UITextField *)textField
{
currentTextField = nil;
}
That reminds me, you'll need to declare currentTextField as a UITextField * in the .m:
#interface ThirdView : UIViewController{
UITextField *currentTextField;
BOOL keyboardIsShown;
}
My viewDidLoad, keyBoardWasShown, etc are a bit different from yours, but I'll just put them all here so you can see how I got it to work:
//This is code you actually add to get the view to scroll.
You should first connect an outlet from the ScrollView to the .h file so these
functions become available.
- (void)viewDidLoad {
//standard screen size is 320 X 460
// ---set the viewable frame of the scroll view---
// scrollView.frame = CGRectMake(0, 0, 320, 460);
//---set the content size of the scroll view---
// [scrollView setContentSize:CGSizeMake(320, 615)];
//the status bar is 20 pixels tall
//the navigation bar is 44 pixels tall
//---set the viewable frame of the scroll view---
//Note: for some reason, the origin (0,44) doesn't take into account the status bar, but it works anyway. However, the height of the scroll view does take it into account. Wierd, but whatever. So you have to make y1 = 44, and y2 = 460-44-20.
scrollView.frame = CGRectMake(0, 44, 320, 416);
//---set the content size of the scroll view---
[scrollView setContentSize:CGSizeMake(320, 571)];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardWasShown:)
name:UIKeyboardDidShowNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardWillBeHidden:)
name:UIKeyboardWillHideNotification object:nil];
[super viewDidLoad];
}
- (void)viewDidUnload
{
[self setPrincipal_Amt:nil];
[self setAPR:nil];
[self setYears:nil];
[self setScrollView:nil];
[super viewDidUnload];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
// Called when the UIKeyboardDidShowNotification is sent.
- (void)keyboardWasShown:(NSNotification*)aNotification
{
if(keyboardIsShown)
return;
NSDictionary* info = [aNotification userInfo];
CGSize kbSize = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;
UIEdgeInsets contentInsets = UIEdgeInsetsMake(0.0, 0.0, kbSize.height, 0.0);
scrollView.contentInset = contentInsets;
scrollView.scrollIndicatorInsets = contentInsets;
// If active text field is hidden by keyboard, scroll it so it's visible
Notice that we've got to add 64 pixels to the keyboard height because the CGRect has
no idea that you have the status bar shown and a navigation bar within your view, so
you have to manually add it in.
CGRect aRect = self.view.frame;
aRect.size.height -= (kbSize.height + 64);
if (!CGRectContainsPoint(aRect, currentTextField.frame.origin) )
{
CGPoint scrollPoint = CGPointMake(0.0, currentTextField.frame.origin.y-kbSize.height + 64);
[scrollView setContentOffset:scrollPoint animated:YES];
}
keyboardIsShown = YES;
}
// Called when the UIKeyboardWillHideNotification is sent
- (void)keyboardWillBeHidden:(NSNotification*)aNotification
{
UIEdgeInsets contentInsets = UIEdgeInsetsZero;
scrollView.contentInset = contentInsets;
scrollView.scrollIndicatorInsets = contentInsets;
keyboardIsShown = NO;
}
//
I hope that helps...even though you did post this a while ago :) It took me a while to get it to work too. Very frustrating.