My problem is that the subviews of my scrollView respond to mouse event even if they are not visible (out of the bounds of the visible part of the scrollView).
I've got this architecture :
CustomView *view01 = [[CustomView alloc] init];
CustomView *view02 = [[CustomView alloc] init];
NSView *contentView = [[NSView alloc] init];
[contentView addSubview:view01];
[contentView addSubview:view02];
NSSCrollView *scrollView = [[NSScrollView alloc] init];
scrollView setDocumentView:contentView];
With the CustomView implementation :
#import "CustomView.h"
#interface CustomView ()
{
NSTrackingArea *trackingArea;
}
#end
#implementation CustomView
-(id)initWithFrame:(NSRect)contentRect
{
if(self = [super initWithFrame:(NSRect)contentRect])
{
[self setFrame:contentRect];
}
return self;
}
- (void)mouseDown: (NSEvent*)event
{
NSLog(#"mouseDown:");
[NSApp preventWindowOrdering];
}
- (BOOL)shouldDelayWindowOrderingForEvent:(NSEvent *)evt
{
return YES;
}
-(BOOL)acceptsFirstResponder
{
return YES;
}
-(void)updateTrackingAreas
{
if(trackingArea != nil)
{
[self removeTrackingArea:trackingArea];
}
int opts = (NSTrackingMouseEnteredAndExited | NSTrackingActiveAlways);
trackingArea = [ [NSTrackingArea alloc] initWithRect:[self bounds]
options:opts
owner:self
userInfo:nil];
[self addTrackingArea:trackingArea];
}
- (void)mouseEntered:(NSEvent *)theEvent
{
NSLog(#"mouseEntered:");
}
- (void)mouseExited:(NSEvent *)theEvent
{
NSLog(#"mouseExited:");
}
- (NSView *)hitTest:(NSPoint)aPoint
{
return NSPointInRect(aPoint, self.frame) ? self : nil;
}
#end
I finally found the problem.
I needed to add the option NSTrackingInVisibleRect in the method :
-(void)updateTrackingAreas
{
if(trackingArea != nil)
{
[self removeTrackingArea:trackingArea];
}
int opts = (NSTrackingMouseEnteredAndExited | NSTrackingActiveAlways| NSTrackingInVisibleRect);
trackingArea = [ [NSTrackingArea alloc] initWithRect:[self bounds]
options:opts
owner:self
userInfo:nil];
[self addTrackingArea:trackingArea];
}
Then, when a part of the CustomView are outside the scrollView, it doesn't respond to mouseEntered and mouseExited.
Related
In building a custom NSButton, I've run into a problem handling highlight behavior. After clicking down on the button, holding, and dragging the cursor outside the button's bounds, mouseExited: and mouseEntered: events are not delivered. I understand the reason why, because in mouseDown: calling [super mouseDown:event]; will block until the click is released.
In researching this I came across this Stack Overflow post which describes the same problem. The solution noted is to add NSTrackingEnabledDuringMouseDrag to the NSTrackingArea options, which I have done, yet I continue to see this problem. I tried the other proposed solution with handling the next events in a loop, but this resulted in odd behavior. The button text color turns black on mouse down instead of highlighting the dimmed color, and it doesn't unhighlight upon releasing the mouse, it remains black.
I am using Xcode 9.3, running on macOS 10.13.4.
Here is my NSButton subclass:
#interface BorderlessButton : NSButton {
NSColor *_tempColor;
}
#property (strong, nonatomic) NSColor *color;
#end
#interface BorderlessButton ()
#property (nonatomic) BOOL pressed;
#end
#implementation BorderlessButton
- (id)init {
if (self = [super init]) {
[self setUp];
}
return self;
}
- (id)initWithFrame:(NSRect)frameRect {
if (self = [super initWithFrame:frameRect]) {
[self setUp];
}
return self;
}
- (id)initWithCoder:(NSCoder *)aDecoder {
if (self = [super initWithCoder:aDecoder]) {
[self setUp];
}
return self;
}
- (void)setUp {
_color = [NSColor redColor];
[self setTitle:self.title];
[self setButtonType:NSButtonTypeMomentaryChange];
[self setBordered:NO];
NSTrackingArea *area = [[NSTrackingArea alloc] initWithRect:self.bounds
options:NSTrackingMouseEnteredAndExited | NSTrackingActiveAlways | NSTrackingEnabledDuringMouseDrag
owner:self
userInfo:nil];
[self addTrackingArea:area];
}
- (void)setTitle:(NSString *)title {
[super setTitle:title];
NSMutableAttributedString *colorTitle = [[NSMutableAttributedString alloc] initWithAttributedString:[self attributedTitle]];
[colorTitle addAttributes:#{NSFontAttributeName: self.font, NSForegroundColorAttributeName: self.color} range:NSMakeRange(0, [colorTitle length])];
[self setAttributedTitle:colorTitle];
}
- (void)setColor:(NSColor *)color {
_color = color;
[self setTitle:self.title];
}
- (void)mouseDown:(NSEvent *)event {
self.pressed = YES;
[self highlight:YES];
[super mouseDown:event]; // this blocks until released
[self mouseUp:event];
}
- (void)mouseUp:(NSEvent *)event {
self.pressed = NO;
[self highlight:NO];
[super mouseUp:event];
}
//FIXME: Not called after mouse press down and hold then exit
- (void)mouseExited:(NSEvent *)event {
if (self.pressed) {
[self highlight:NO];
}
[super mouseExited:event];
}
- (void)mouseEntered:(NSEvent *)event {
if (self.pressed) {
[self highlight:YES];
}
[super mouseEntered:event];
}
- (void)highlight:(BOOL)flag {
if (flag) {
if (self.isEnabled) {
NSColor *dimmedColor = [self dimmedColor];
_tempColor = _color;
self.color = dimmedColor;
[self setTitle:self.title];
}
} else {
if (self.isEnabled) {
self.color = _tempColor;
[self setTitle:self.title];
}
}
}
- (NSColor *)dimmedColor {
return [self.color colorWithAlphaComponent:0.5];
}
#end
I am using a Toggle button in XIB,
in AwakeFromNib :I set title and BG image of the button.
Code works very well till OSX 10.10.5 and below but on higher versions when I click on button the text gets removed.
Set attributed Title
[self.deleteButton setWantsLayer:YES];
self.deleteButton.layer.backgroundColor = [self setButtonBGColor];
[self setButtonTitleFor:self.deleteButton
toString:#"Delete"];
[self.deleteButton setLayerContentsRedrawPolicy:NSViewLayerContentsRedrawOnSetNeedsDisplay];
- (void)setButtonTitleFor:(NSButton*)button toString:(NSString*)title
{
NSAttributedString *attrString = [self getAttributedStringForString:title];
[button setAttributedTitle:attrString];
}
Any idea hat should be done.
So finally got it right
Subclassing NSButtonCell helped me out
RPButtonTextTopCell.h
#interface RPButtonTextTopCell : NSButtonCell
#end
RPButtonTextTopCell.m
#implementation RPButtonTextTopCell
-(id) init
{
self = [super init];
if (self) {
}
return self;
}
-(id) initWithCoder:(NSCoder *)decoder
{
return [super initWithCoder:decoder];
}
-(id) initTextCell:(NSString *)string
{
return [super initTextCell:string];
}
-(NSRect)titleRectForBounds:(NSRect)theRect
{
NSRect titleFrame = [super titleRectForBounds:theRect];
NSSize titleSize = [[self attributedStringValue] size];
titleFrame.origin.y = (theRect.origin.y - (theRect.size.height-titleSize.height)*0.5) + 5;
return titleFrame;
}
#end
Utilizing This Custom Class
RPButtonTextTopCell *deleteCell = [[RPButtonTextTopCell alloc] init];
deleteCell.backgroundColor = [self setNSButtonBGColor];
[self.deleteButton setCell:deleteCell];
[self setButtonTitleFor:self.deleteButton
toString:#"Delete"];
[self.deleteButton setAction:#selector(deleteButtonClicked:)];
[self.deleteButton setTarget:self];
and its solved....
In Mavericks, the methods to open and close NSSheets has changed. And to make it tougher, the Release Notes do not match the current documentation (see below).
I'm trying to do this:
MainSheetController (NSWindowController):
-(IBAction)callSheet:(id)sender {
[sheetController openSheet];
}
SheetController (NSObject):
(void)openSheet {
[[NSBundle mainBundle] loadNibNamed:sheetName owner:self topLevelObjects:nil];
NSLog(#"1");
[self.mainWindowController.window beginSheet:self.sheet completionHandler:nil];
NSLog(#"2");
}
I get to 2, with no errors or warnings, but no sheet..
Current Documentation:
#if NS_BLOCKS_AVAILABLE
- (void)beginSheet:(NSWindow *)sheetWindow completionHandler:(void (^)(NSModalResponse returnCode))handler NS_AVAILABLE_MAC(10_9);
- (void)beginCriticalSheet:(NSWindow *)sheetWindow completionHandler:(void (^)(NSModalResponse returnCode))handler NS_AVAILABLE_MAC(10_9);
#endif
- (IBAction)userButtonPressed:(id)sender {
UserLoginWindowController * wc = [UserLoginWindowController new];
// we keep a reference, so the WC doesn't deallocate
self.modalWindowController = wc;
[[self window] beginSheet:[wc window] completionHandler:^(NSModalResponse returnCode) {
self.modalWindowController = nil;
}];
}
in the UserLoginWindowController
- (IBAction)cancelButtonPressed:(id)sender {
[[[self window] sheetParent] endSheet:[self window] returnCode:NSModalResponseCancel];
}
- (IBAction)showSheet:(id)sender
{
if (_windowController == nil)
{
_windowController = [MyWindowController new];
}
[[self window] beginSheet:[_windowController window] completionHandler:^(NSModalResponse returnCode)
{
}];
}
// And inside your MyWindowController class:
- (id)init
{
self = [super initWithWindowNibName:#"MyWindowNibName"]; // TODO: Change the name of your NIB
return self;
}
In your nib file, make sure the "Visible At Launch" flag is unticked for the window.
I figured out how to do this. Hope it's ok to post..
MainWindow.h
#interface MainWindowController : NSWindowController {
NSString *sheetName;
IBOutlet NSWindow *sheet;
id result1;
id result2;
...
id resultn;
}
#property (strong) NSString *sheetName;
#property (strong, nonatomic) IBOutlet NSWindow *sheet;
-(IBAction)callSheet0:(id)sender;
-(IBAction)callSheet1:(id)sender;
-(IBAction)callSheetn:(id)sender;
- (void)openSheet;
- (IBAction)save:(id)sender;
- (IBAction)cancel:(id)sender;
MainWindow.m
- (void)windowDidLoad
{
NSLog(#"%s", __FUNCTION__);
[super windowDidLoad];
sheetName = [[NSString alloc] init];
}
-(IBAction)callSheet0:(id)sender {
NSLog(#"%s", __FUNCTION__);
sheetName = #"Sheet0";
[self openSheet];
}
....
-(IBAction)callSheetn:(id)sender {
NSLog(#"%s", __FUNCTION__);
sheetName = #"Sheetn";
[self openSheet];
- (void)openSheet {
NSLog(#"%s", __FUNCTION__);
NSLog(#"sheetName: %#",sheetName );
[[NSBundle mainBundle] loadNibNamed:sheetName owner:self topLevelObjects:nil];
[self.window beginSheet:self.sheet completionHandler:nil];
}
- (void)save:(NSButton*)sender {
switch ([sender tag]) {
case 0:
[self doSave1];
result = #"1";
break;
case 1:
[self doSave2];
result = #"2";
break;
case n:
[self doSaven];
result = #"x";
break;
}
[self endSheet:self.sheet returnCode:result];
}
- (IBAction)cancel:(id)sender {
NSLog(#"%s", __FUNCTION__);
result = #"0";
[self endSheet:self.sheet returnCode:result];
// returnCode is optional
}
//endSheet:(NSWindow *)sheetWindow {
- (void)endSheet:(NSWindow *)sheetWindow returnCode:returnCode {
//NSLog(#"%s", __FUNCTION__);
[sheetWindow orderOut:self];
}
- (void)save:(NSButton*)sender {
switch ([sender tag]) {
case 0:
[self doSave1];
result = #"1";
break;
case n:
[self doSave3];
result = #"3";
break;
}
[self endSheet:self.sheet returnCode:result];
}
With this method, new in 10.9,I don't need a special sheet controller, and control remains quote local.
I have a MainViewController with a splitview in it. Next I have two viewcontroller to control each of the views in the splitview. I want to be able to drag and drop a file in one of those views.
But I can't seem to get the dragging to work? There's no "plus-sign" on the file when dragged to the view and dropping it doesn't do anything either.
What am I doing wrong?
First here's MainViewController.m
fileViewController = [[FileViewController alloc] initWithNibName:#"FileViewController" bundle:nil];
terminalViewController = [[TerminalViewController alloc] initWithNibName:#"TerminalViewController" bundle:nil];
[splitView replaceSubview:[[splitView subviews] objectAtIndex:0] with:[fileViewController view]];
[splitView replaceSubview:[[splitView subviews] objectAtIndex:1] with:[terminalViewController view]];
Next, my code to handle the dragging in the FileViewController
#dynamic isHighlighted;
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
NSLog(#"registering");
[self.view registerForDraggedTypes:[NSArray arrayWithObjects: NSFilenamesPboardType, nil]];
}
return self;
}
- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender {
NSLog(#"[%# %#]", NSStringFromClass([self class]), NSStringFromSelector(_cmd));
NSPasteboard *pboard = [sender draggingPasteboard];
if ([[pboard types] containsObject:NSFilenamesPboardType]) {
NSArray *paths = [pboard propertyListForType:NSFilenamesPboardType];
for (NSString *path in paths) {
NSError *error = nil;
NSString *utiType = [[NSWorkspace sharedWorkspace]
typeOfFile:path error:&error];
if (![[NSWorkspace sharedWorkspace]
type:utiType conformsToType:(id)kUTTypeFolder]) {
[self setHighlighted:NO];
return NSDragOperationNone;
}
}
}
[self setHighlighted:YES];
return NSDragOperationEvery;
}
- (void)draggingExited:(id <NSDraggingInfo>)sender {
[self setHighlighted:NO];
}
- (BOOL)prepareForDragOperation:(id <NSDraggingInfo>)sender {
return YES;
}
- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender {
[self setHighlighted:NO];
return YES;
}
- (void)concludeDragOperation:(id )sender {
[self.view setNeedsDisplay:YES];
} // end concludeDragOperation
- (BOOL)isHighlighted {
return isHighlighted;
}
- (void)setHighlighted:(BOOL)value {
isHighlighted = value;
[self.view setNeedsDisplay:YES];
}
- (void)drawRect:(NSRect)frame {
[self.view drawRect:frame];
if (isHighlighted) {
[NSBezierPath setDefaultLineWidth:6.0];
[[NSColor keyboardFocusIndicatorColor] set];
[NSBezierPath strokeRect:frame];
}
}
I didn't seem to fix it, and because no one gave me any answers, I fixed it by making an NSView subclass and adding it to my viewcontroller like this.
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
draggable = [[DragView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)];
[[self view] addSubview:draggable];
}
return self;
}
I've never done OS X programming, but the problem is that since you're registering self.view to receive drag and drop, your self.view should handle the drag and drop operations and not your view controller. So you're correct in subclassing NSView to DragView, and you should pass the drag operations back to the view controller.
Also, I believe - (NSDragOperation)draggingUpdated:(id<NSDraggingInfo>)sender should be implemented.
I need to find my current location with CoreLocation, I tried multiple methods but so far my CLLocationManager has only returned 0's.. (0.000.00.000).
Here's my code (updated to work):
Imports:
#import <CoreLocation/CoreLocation.h>
Declared:
IBOutlet CLLocationManager *locationManager;
IBOutlet UILabel *latLabel;
IBOutlet UILabel *longLabel;
Functions:
- (void)getLocation { //Called when needed
latLabel.text = [NSString stringWithFormat:#"%f", locationManager.location.coordinate.latitude];
longLabel.text = [NSString stringWithFormat:#"%f", locationManager.location.coordinate.longitude];
}
- (void)viewDidLoad {
locationManager = [[CLLocationManager alloc] init];
locationManager.distanceFilter = kCLDistanceFilterNone; // whenever we move
locationManager.desiredAccuracy = kCLLocationAccuracyHundredMeters; // 100 m
[locationManager startUpdatingLocation];
}
You can find your location using CoreLocation like this:
import CoreLocation:
#import <CoreLocation/CoreLocation.h>
Declare CLLocationManager:
CLLocationManager *locationManager;
Initialize the locationManager in viewDidLoad and create a function that can return the current location as an NSString:
- (NSString *)deviceLocation {
return [NSString stringWithFormat:#"latitude: %f longitude: %f", locationManager.location.coordinate.latitude, locationManager.location.coordinate.longitude];
}
- (void)viewDidLoad
{
locationManager = [[CLLocationManager alloc] init];
locationManager.distanceFilter = kCLDistanceFilterNone; // whenever we move
locationManager.desiredAccuracy = kCLLocationAccuracyHundredMeters; // 100 m
[locationManager startUpdatingLocation];
}
And calling the deviceLocation function will return the location as expected:
NSLog(#"%#", [self deviceLocation]);
This is just an example. Initializing CLLocationManager without the user being ready for it isn't a good idea. And, of course, locationManager.location.coordinate can be used to get latitude and longitude at will after CLLocationManager has been initialized.
Don't forget to add the CoreLocation.framework in your project settings under the Build Phases tab (Targets->Build Phases->Link Binary).
With CLLocationManager you don't necessary get the location information immediately. The GPS and other devices that obtain location information might not be initialized. They can take a while before they have any information. Instead you need to create a delegate object that responds to locationManager:didUpdateToLocation:fromLocation: and then set it as the delegate of the location manager.
see here
Here you can show current location with annotation details
in ViewController.h
#import <UIKit/UIKit.h>
#import <CoreLocation/CoreLocation.h>
//My
#import <MapKit/MapKit.h>
#import <MessageUI/MFMailComposeViewController.h>
#interface ViewController : UIViewController<CLLocationManagerDelegate,MKMapViewDelegate,MFMailComposeViewControllerDelegate>
{
IBOutlet UILabel *lblLatitiude;
IBOutlet UILabel *lblLongitude;
IBOutlet UILabel *lblAdress;
}
//My
#property (nonatomic, strong) IBOutlet MKMapView *mapView;
-(IBAction)getMyLocation:(id)sender;
#end
in ViewController.m
#import "ViewController.h"
#interface ViewController ()
#end
#implementation ViewController{
CLLocationManager *locationManager;
CLGeocoder *geocoder;
CLPlacemark *placemark;
}
#synthesize mapView;
- (void)viewDidLoad
{
[super viewDidLoad];
locationManager = [[CLLocationManager alloc] init];
geocoder = [[CLGeocoder alloc] init];
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
NSMutableDictionary *defaultsDictionary = [NSMutableDictionary dictionaryWithObjectsAndKeys:#"pradipvdeore#gmail.com", #"fromEmail",
#"pavitrarupani89#gmail.com", #"toEmail",
#"smtp.gmail.com", #"relayHost",
#"mobileapp.qa#gmail.com", #"login",
#"mobile#123", #"pass",
[NSNumber numberWithBool:YES], #"requiresAuth",
[NSNumber numberWithBool:YES], #"wantsSecure", nil];
[userDefaults registerDefaults:defaultsDictionary];
self.mapView.delegate=self;
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#pragma mark - Custom Methods
-(IBAction)getMyLocation:(id)sender{
locationManager.delegate = self;
locationManager.desiredAccuracy = kCLLocationAccuracyBest;
[locationManager startUpdatingLocation];
}
#pragma mark - CLLocationManagerDelegate
- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error
{
NSLog(#"didFailWithError: %#", error);
UIAlertView *errorAlert = [[UIAlertView alloc]
initWithTitle:#"Error" message:#"Failed to Get Your Location" delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil];
[errorAlert show];
}
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation
{
NSLog(#"didUpdateToLocation: %#", newLocation);
CLLocation *currentLocation = newLocation;
if (currentLocation != nil) {
lblLongitude.text = [NSString stringWithFormat:#"%.8f", currentLocation.coordinate.longitude];
lblLatitiude.text = [NSString stringWithFormat:#"%.8f", currentLocation.coordinate.latitude];
}
// Stop Location Manager
[locationManager stopUpdatingLocation];
// Reverse Geocoding
NSLog(#"Resolving the Address");
[geocoder reverseGeocodeLocation:currentLocation completionHandler:^(NSArray *placemarks, NSError *error) {
NSLog(#"Found placemarks: %#, error: %#", placemarks, error);
if (error == nil && [placemarks count] > 0) {
placemark = [placemarks lastObject];
lblAdress.text = [NSString stringWithFormat:#"%# %#\n%# %#\n%#\n%#",
placemark.subThoroughfare, placemark.thoroughfare,
placemark.postalCode, placemark.locality,
placemark.administrativeArea,
placemark.country];
MKCoordinateRegion region = MKCoordinateRegionMakeWithDistance(currentLocation.coordinate, 800, 800);
[self.mapView setRegion:[self.mapView regionThatFits:region] animated:YES];
// Add an annotation
MKPointAnnotation *point = [[MKPointAnnotation alloc] init];
point.coordinate = currentLocation.coordinate;
point.title = #"Where am I?";
point.subtitle = [NSString stringWithFormat:#"%# %#\n%# %#\n%#\n%#",
placemark.subThoroughfare, placemark.thoroughfare,
placemark.postalCode, placemark.locality,
placemark.administrativeArea,
placemark.country];
[self.mapView addAnnotation:point];
} else {
NSLog(#"%#", error.debugDescription);
}
} ];
}
//My
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation
{
MKAnnotationView *annotationView = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:#"loc"];
annotationView.canShowCallout = YES;
annotationView.rightCalloutAccessoryView = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
return annotationView;
}
- (void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control
{
[self getSignScreenShot];
MFMailComposeViewController* controller = [[MFMailComposeViewController alloc] init];
controller.mailComposeDelegate = self;
[controller setSubject:#"My Subject"];
[controller setMessageBody:#"Hello there." isHTML:NO];
if (controller) [self presentModalViewController:controller animated:YES];
}
- (void)mailComposeController:(MFMailComposeViewController*)controller
didFinishWithResult:(MFMailComposeResult)result
error:(NSError*)error;
{
if (result == MFMailComposeResultSent) {
NSLog(#"It's away!");
}
[self dismissModalViewControllerAnimated:YES];
}
//-----------------------------------------------------------------------------------
//This methos is to take screenshot of map
//-----------------------------------------------------------------------------------
-(UIImage *)getSignScreenShot
{
CGRect rect = CGRectMake(self.mapView.frame.origin.x,self.mapView.frame.origin.y-50,self.mapView.frame.size.width+60,self.mapView.frame.size.height+15);
UIGraphicsBeginImageContextWithOptions(self.mapView.frame.size, NO, 1.0);
[self.mapView.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *screenshot = UIGraphicsGetImageFromCurrentImageContext();
CGImageRef imageRef = CGImageCreateWithImageInRect([screenshot CGImage], rect);
UIImage *newImage = [UIImage imageWithCGImage:imageRef];
return newImage;
}