I have a video playing app where an NSView is shown, tracked to the mouse coordinates, to show the time position when the user hovers over a certain area.
This works perfectly 70% of the time, however it frequently doesn't fire at all. The times when this is most likely to occur seem to be when bringing the mouse inside the view for the first time and after hovering the mouse outside of the area and then back inside again.
The code inside the NSView subclass is as follows:
- (void)viewDidMoveToWindow
{
if ([self window]) {
[self resetTrackingRect];
}
}
- (void)clearTrackingRect
{
if (rolloverTrackingRectTag > 0)
{
[self removeTrackingRect:rolloverTrackingRectTag];
rolloverTrackingRectTag = 0;
}
}
- (void)resetTrackingRect
{
[self clearTrackingRect];
rolloverTrackingRectTag = [self addTrackingRect:[self visibleRect]
owner:self userData:NULL assumeInside:NO];
}
- (void)resetCursorRects
{
[super resetCursorRects];
[self resetTrackingRect];
}
- (void)mouseEntered:(NSEvent *)theEvent
{
// Only ask for mouse move events when inside rect because they are expensive
[[self window] setAcceptsMouseMovedEvents:YES];
[[self window] makeFirstResponder:self];
// Tells the observer to show the time view
[[NSNotificationCenter defaultCenter] postNotificationName:#"MWTimelineHover" object:self userInfo:[NSDictionary dictionaryWithObjectsAndKeys:theEvent,#"event",nil]];
}
- (void)mouseExited:(NSEvent *)theEvent
{
[[self window] setAcceptsMouseMovedEvents:NO];
[[self window] resignFirstResponder];
// Tells the observer to hide the time view
[[NSNotificationCenter defaultCenter] postNotificationName:#"MWTimelineHoverLeave" object:self];
}
- (void)mouseMoved:(NSEvent *)theEvent
{
[super mouseMoved:theEvent];
// Tells the observer to show the time view
[[NSNotificationCenter defaultCenter] postNotificationName:#"MWTimelineHover" object:self userInfo:[NSDictionary dictionaryWithObjectsAndKeys:theEvent,#"event",nil]];
}
Note: on the occasions when it stalls, mouseExited is not called and the view has not lost firstResponder status. I am also not dragging the mouse, simply moving it normally.
You need to use NSTrackingArea. Here is reference NSTrackingArea Class Reference.
- (void)commonInit {
CGRect rect = CGRectMake(0.0f, 0.0f, self.frame.size.width, self.frame.size.height);
NSTrackingAreaOptions options = NSTrackingActiveInKeyWindow | NSTrackingMouseMoved | NSTrackingInVisibleRect;
_trackingArea = [[NSTrackingArea alloc] initWithRect:rect options:options owner:self userInfo:nil];
[self addTrackingArea:_trackingArea];
}
Hope it helps you.
Related
I try to call a method after a delay when the user start to dragging a scrollView.
This block below is called but the action define in this performselector: is called only when I stop to drag the scrollView
- (void)viewDidLoad {
[super viewDidLoad];
UIScrollView *sv = [[UIScrollView alloc] initWithFrame:self.view.frame];
sv.delegate = self;
sv.backgroundColor = [UIColor redColor];
[sv setContentSize:CGSizeMake(1000, 200)];
[self.view addSubview:sv];
}
-(void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{
NSLog(#"hey");
[self performSelector:#selector(myAction) withObject:nil afterDelay:3];
}
- (void)myAction
{
NSLog(#"Called 3secondes after begin dragging");
}
I also try with a NSTimer and in the background thread but the problem is the same...
Any Idea?
If you want to have the callback fired while you're still dragging, you must schedule it for Common Run Loop modes, like so:
[self performSelector:#selector(myAction) withObject:nil afterDelay:3 inModes:#[NSRunLoopCommonModes]];
That'll do the trick :)
I am experimenting with this code.
In one of viewController's I am using next snippet:
- (BOOL)textView:(UITextView *)textView1 shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text
{
if([text isEqualToString:#"\n"])
{
[textView resignFirstResponder];
return NO;
}
return YES;
}
I found in web to dismissing the keyboard by pressing returnButton. It works well when I am calling it from this viewController. In root KLNoteViewController I am adding notification in handle state changes-method:
- (void) setState:(KLControllerCardState)state animated:(BOOL) animated
{
[[NSNotificationCenter defaultCenter] postNotificationName:#"refresh" object:self];
if (animated)
{
[UIView animateWithDuration:self.noteViewController.cardAnimationDuration animations:^{
[self setState:state animated:NO];
}];
return;
}
//Full Screen State
if (state == KLControllerCardStateFullScreen)
{
[self expandCardToFullSize: animated];
[self setYCoordinate: 0];
}
//Default State
else if (state == KLControllerCardStateDefault)
{
[self shrinkCardToScaledSize: animated];
[self setYCoordinate: originY];
}
//Hidden State - Bottom
else if (state == KLControllerCardStateHiddenBottom)
{
//Move it off screen and far enough down that the shadow does not appear on screen
[self setYCoordinate: self.noteViewController.view.frame.size.height + abs(self.noteViewController.cardShadowOffset.height)*3];
}
//Hidden State - Top
else if (state == KLControllerCardStateHiddenTop)
{
[self setYCoordinate: 0];
}
//Notify the delegate of the state change (even if state changed to self)
KLControllerCardState lastState = self.state;
//Update to the new state
[self setState:state];
//Notify the delegate
if ([self.delegate respondsToSelector:#selector(controllerCard:didChangeToDisplayState:fromDisplayState:)]) {
[self.delegate controllerCard:self
didChangeToDisplayState:state fromDisplayState: lastState];
}
}
and add adding observer:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(dismissKeyboard) name:#"refresh" object:nil];
in every viewControllers -viewDidLoad. So when this observer is used with UITextField everything works fine, in viewController when I am using UIText View I have crash with log:
[NSStackBlock isEqualToString:]: unrecognized selector sent to
instance 0xbfffd228
I searched in web but found only two links with explanation of what is the NSStackBlock is and they are not informative well for me to solve the problem. Can somebody explain what it can be?
I am using Xcode 4.5 and targeting iOS 5 and above.
I have a popover that allows a user to change the background of the underlying view.
When tapping on the "button", it requires that I tap twice to see the change.
I am using NSNotification. I have tried Delegation, yet it does nothing.
Any help or tips would be greatly appreciated.
From viewWillAppear:
// nav button is for background image selection
navigationBtn.tag = buttonTag;
[navigationBtn setBackgroundImage:[UIImage imageNamed:tempImage] forState:UIControlStateNormal];
if (selectFlag) {
[navigationBtn addTarget:self action:#selector(BGSelected:) forControlEvents:UIControlEventTouchUpInside];
} else {
navigationBtn.adjustsImageWhenHighlighted = NO;
}
And the method for selection:
- (void)BGSelected:(id)sender {
self.bgtag = [sender tag];
SettingsDAO *settingsDao = [[SettingsDAO alloc] init];
if ([self.currentView isEqualToString:#"New_Journal"])
{
NSLog(#"Update Journals Background");
[settingsDao updateJournalBackgroundId:self.journalId withBackgroundId:self.bgtag];
}
// notification to let EntryView know the background has changed
[[NSNotificationCenter defaultCenter] postNotificationName:#"aBackgroundChanged"
object:self];
[settingsDao release];
}
the NSNotification Method:
- (void)aBackgroundChanged:(NSNotification *)notification {
[self invalidate];
[self reloadSettings];
}
If you are using storyboard, and your popover is a segue you can do like that:
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
// perform all the operations...ecc
[[(UIStoryboardPopoverSegue*)segue popoverController] setDelegate:self];
}
Then you have to make sure your caller controller has all the delegate methods.
Or, with NSNotification:
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
// perform all the operations...ecc
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:#selector(changeBackground:)
name:BackgroundHasChangedNotification
object:[segue destinationViewController]];
}
Then just remember to remove observer in dealloc, and in change background method:
-(void)changeBackground:(NSNotification*)notification {
NSDictionary *userInfo = notification.userInfo;
// I assume you are using background name
NSString *backgroundName = [userInfo objectForKey:BackgroundOneConstant];
// do the rest....
}
Right now, I have basic code for moving the textfield above the keyboard when you start editing. However, the size of the textfield varies based on device and orientation. So, I wrote a crude way of doing it, which doesn't stay consistently right above the keyboard, but instead will go up further when you rotate it, and so it doesn't look as professional as I would like.
The basic sense of my question is if there is a logic for getting the size of the keyboard based on device and orientation and using that value automatically and hopefully faster than this.
If that is the best way, please let me know. Otherwise, please provide input. Here is the code that I have.
(This is just the move-up code, not the move down code, in order to prevent taking up too much space)
- (void)textFieldDidBeginEditing:(UITextField *)textField {
//Get Device Type
NSString *deviceType = [[UIDevice currentDevice] model];
//Animate Text Field
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDelegate:self];
[UIView setAnimationDuration:0.4];
[UIView setAnimationBeginsFromCurrentState:YES];
if ([deviceType isEqualToString:#"iPhone"]) {
//Size For iPhone
googleBar.frame = CGRectMake(googleBar.frame.origin.x - 62.0, (googleBar.frame.origin.y - 210.0), googleBar.frame.size.width + 120.0, googleBar.frame.size.height);
} else if ([deviceType isEqualToString:#"iPad"]) {
//Size for iPad
googleBar.frame = CGRectMake(googleBar.frame.origin.x - 62.0, (googleBar.frame.origin.y - 320.0), googleBar.frame.size.width + 120.0, googleBar.frame.size.height);
} else if ([deviceType isEqualToString:#"iPod touch"]) {
//Size For iPod Touch
googleBar.frame = CGRectMake(googleBar.frame.origin.x - 62.0, (googleBar.frame.origin.y - 210.0), googleBar.frame.size.width + 120.0, googleBar.frame.size.height);
}
[UIView commitAnimations];
}
What you really want to do is observe the UIKeyboard(Did|Will)(Show|Hide) notifications. They contain in their userInfo dictionaries the beginning and ending frame, as well as the correct animation curve and durations.
So after observing this notification, when it's posted move your text field based on the size of the frame passed in the notification, according to the animation hints provided.
You can see more information in the UIWindow class reference's "notifications" section: https://developer.apple.com/library/ios/#documentation/uikit/reference/UIWindow_Class/UIWindowClassReference/UIWindowClassReference.html
Below is a sample view controller implementation. The nib for this view controller was just a single text field, with an outlet connected to it, and the text field's delegate set to the view controller.
#interface ViewController ()
- (void)viewControllerInit;
#end
#implementation ViewController
#synthesize textField;
- (id)initWithCoder:(NSCoder *)coder {
self = [super initWithCoder:coder];
if (self) {
[self viewControllerInit];
}
return self;
}
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil])
{
[self viewControllerInit];
}
return self;
}
- (void)viewControllerInit
{
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];
}
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
#pragma mark - Notification Handlers
- (void)keyboardWillShow:(NSNotification *)notification
{
// I'll try to make my text field 20 pixels above the top of the keyboard
// To do this first we need to find out where the keyboard will be.
NSValue *keyboardEndFrameValue = [[notification userInfo] objectForKey:UIKeyboardFrameEndUserInfoKey];
CGRect keyboardEndFrame = [keyboardEndFrameValue CGRectValue];
// When we move the textField up, we want to match the animation duration and curve that
// the keyboard displays. So we get those values out now
NSNumber *animationDurationNumber = [[notification userInfo] objectForKey:UIKeyboardAnimationDurationUserInfoKey];
NSTimeInterval animationDuration = [animationDurationNumber doubleValue];
NSNumber *animationCurveNumber = [[notification userInfo] objectForKey:UIKeyboardAnimationCurveUserInfoKey];
UIViewAnimationCurve animationCurve = [animationCurveNumber intValue];
// UIView's block-based animation methods anticipate not a UIVieAnimationCurve but a UIViewAnimationOptions.
// We shift it according to the docs to get this curve.
UIViewAnimationOptions animationOptions = animationCurve << 16;
// Now we set up our animation block.
[UIView animateWithDuration:animationDuration
delay:0.0
options:animationOptions
animations:^{
// Now we just animate the text field up an amount according to the keyboard's height,
// as we mentioned above.
CGRect textFieldFrame = self.textField.frame;
textFieldFrame.origin.y = keyboardEndFrame.origin.y - textFieldFrame.size.height - 40; //I don't think the keyboard takes into account the status bar
self.textField.frame = textFieldFrame;
}
completion:^(BOOL finished) {}];
}
- (void)keyboardWillHide:(NSNotification *)notification
{
NSNumber *animationDurationNumber = [[notification userInfo] objectForKey:UIKeyboardAnimationDurationUserInfoKey];
NSTimeInterval animationDuration = [animationDurationNumber doubleValue];
NSNumber *animationCurveNumber = [[notification userInfo] objectForKey:UIKeyboardAnimationCurveUserInfoKey];
UIViewAnimationCurve animationCurve = [animationCurveNumber intValue];
UIViewAnimationOptions animationOptions = animationCurve << 16;
[UIView animateWithDuration:animationDuration
delay:0.0
options:animationOptions
animations:^{
self.textField.frame = CGRectMake(20, 409, 280, 31); //just some hard coded value
}
completion:^(BOOL finished) {}];
}
#pragma mark - View lifecycle
- (void)viewDidUnload
{
[self setTextField:nil];
[super viewDidUnload];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
#pragma mark - UITextFieldDelegate
- (BOOL)textFieldShouldReturn:(UITextField *)textField
{
[self.textField resignFirstResponder];
return YES;
}
#end
Using notification keyboard register , you can place your textfield inside a scroll and manage the content offset of scroll to turn the actual first responder above keyboard if it's neccesary.
so after register controller to keybard appear , you must to obtain the gap between keyboard origin and scroll origin relative to parent.
you must know if an specific first responder can change content offset of scroll, therefore is neccesary to know the possible bounds between keyboard origin and first responder.
by the way you need to know the gap betwen the scroll content offset and the first responder for place your first responder in specific position.
#interface MainViewController : UIViewController
#property (strong, nonatomic) IBOutlet UIScrollView *scroll;
#end
#interface MainViewController ()
{
CGPoint scrollOffset;
}
#end
#implementation MainViewController
#synthesize scroll
-(void)viewWillAppear:(BOOL)animated
{
[[ NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyboardWillAppear:) name:UIKeyboardDidShowNotification object:nil];
[[ NSNotificationCenter defaultCenter ] addObserver:self selector:#selector(keyboardWillDisAppear:) name:UIKeyboardDidHideNotification object:nil];
}
-(void)viewWillDisappear:(BOOL)animated
{
[[ NSNotificationCenter defaultCenter ] removeObserver:self ];
}
-(void)keyboardWillAppear:(NSNotification*) note
{
const CGFloat default_gap = 25.0f;
NSValue *keyBoardEndFrameValue = [[ note userInfo ] objectForKey:UIKeyboardFrameEndUserInfoKey ];
CGRect keyBoardFrame = [ keyBoardEndFrameValue CGRectValue ];
offset = scroll.contentOffset;
UIWindow *window = [[ UIApplication sharedApplication ] keyWindow];
UITextField *textField = (UITextField*)[ window performSelector:#selector(firstResponder) ];
//Gap between keyboard origin and the scroll origin, relative to parent.
CGFloat distanceRelativeToParent = keyBoardFrame.origin.y - scroll.frame.origin.y;
//Distance between superview to textfield inside scroll. to determine if it's necesary to scroll.
CGFloat bound = (textField.frame.origin.y + textField.frame.size.height)+scroll.frame.origin.y;
CGFloat gapScroll = textField.frame.size.height+default_gap;
if( bound >= keyBoardFrame.origin.y )
{
[ UIView animateWithDuration:.3 delay:0.0 options:UIViewAnimationCurveEaseOut
animations:^{
[ scroll setContentOffset:CGPointMake(0, textField.frame.origin.y - distanceRelativeToParent + gapScroll ) animated:YES ];
}
completion:^(BOOL finished){
}];
}
}
-(void) keyboardWillDisAppear:(NSNotification*) note
{
[ scroll setContentOffset:offset animated:YES ];
}
#end
UIViewControllers have a property called interfaceOrientation and the function UIInterfaceOrientationIsPortrait/Landscape so essentially you can do:
if(UIInterfaceOrientationIsPortrait(self.interfaceOrientation){
//portrait logic
}
else{
//landcapeLogic
}
inside for each the iPhone and iPad in your view controller. From there you can do your pixel measurement way as you had done before, because as far as I know that's the simplest way to do it.
P.S. There is a function to check for landscape checker too, but if the first if statement is false meaning the device is not in portrait, then it must be in landscape hence the plain else.
I have a view with several embedded UITextFields, this UIView is subordinated to a UIScrollView in IB. Each text field is supposed to invoke a method called updateText defined in the viewcontroller implementation file when the user is done editing the field. For some reason, the method updateText never gets invoked. Anyone have any ideas how to go about fixing this? The method fired off just fine when the UIScrollView was not present in the project but the keyboard would cover the text fields during input, which was annoying. Now my textfields move up above the keyboard when it appears, but won't fire off the method when done editing.
Here is my implementation file:
#import "MileMarkerViewController.h"
#implementation MileMarkerViewController
#synthesize scrollView,milemarkerLogDate,milemarkerDesc,milemarkerOdobeg,milemarkerOdoend,milemarkerBusiness,milemarkerPersonal,milemarker;
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) {
// Initialization code
}
return self;
}
- (BOOL) textFieldShouldReturn: (UITextField*) theTextField {
return [theTextField resignFirstResponder];
}
- (void)viewDidLoad {
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver: self
selector: #selector(keyboardWasShown:)
name: UIKeyboardDidShowNotification
object: nil];
[[NSNotificationCenter defaultCenter] addObserver: self
selector: #selector(keyboardWasHidden:)
name: UIKeyboardDidHideNotification
object: nil];
keyboardShown = NO; // 1
[scrollView setContentSize: CGSizeMake( 320, 480)]; // 2
}
- (void)keyboardWasShown:(NSNotification*)aNotification {
if (keyboardShown) return;
NSDictionary* info = [aNotification userInfo];
// Get the size of the keyboard.
NSValue* aValue = [info objectForKey:UIKeyboardBoundsUserInfoKey];
CGSize keyboardSize = [aValue CGRectValue].size;
// Resize the scroll view (which is the root view of the window)
CGRect viewFrame = [scrollView frame];
viewFrame.size.height -= keyboardSize.height;
scrollView.frame = viewFrame;
// Scroll the active text field into view.
CGRect textFieldRect = [activeField frame];
[scrollView scrollRectToVisible:textFieldRect animated:YES];
keyboardShown = YES;
}
- (void)keyboardWasHidden:(NSNotification*)aNotification {
NSDictionary* info = [aNotification userInfo];
// Get the size of the keyboard.
NSValue* aValue = [info objectForKey:UIKeyboardBoundsUserInfoKey];
CGSize keyboardSize = [aValue CGRectValue].size;
// Reset the height of the scroll view to its original value
CGRect viewFrame = [scrollView frame];
viewFrame.size.height += keyboardSize.height;
[scrollView scrollRectToVisible:CGRectMake(0, 0, 1, 1) animated:YES];
scrollView.frame = viewFrame;
keyboardShown = NO;
}
- (void)textFieldDidBeginEditing:(UITextField *)textField {
activeField = textField;
}
- (void)textFieldDidEndEditing:(UITextField *)textField {
activeField = nil;
}
- (IBAction)updateText:(id) sender {
NSLog(#"You just entered: %#",self.milemarkerLogDate.text);
self.milemarker.logdate = self.milemarkerLogDate.text;
self.milemarker.desc = self.milemarkerDesc.text;
self.milemarker.odobeg = self.milemarkerOdobeg.text;
self.milemarker.odoend = self.milemarkerOdoend.text;
self.milemarker.business = self.milemarkerBusiness.text;
self.milemarker.personal = self.milemarkerPersonal.text;
NSLog(#"Original textfield is set to: %#",self.milemarker.logdate);
[self.milemarker updateText];
}
- (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)dealloc {
[super dealloc];
}
#end
I think you have already known a solution because of post date.
You can implement textFieldShouldReturn method and resign first responder from textField in the method.
textFieldDidEndEditing is called after textField resign first responder.