I am creating a work queue to perform tasks in the background. The code is below. The problem is that selector called by the timer is called twice every period, by 2 different timers.
The queue (UpdateController) is created in didFinishLaunchingWithOptions of the AppDelegate:
...
[self setUpdateController:[[FFUpdateController alloc] initWithRootDetailViewController:rdvc]];
[[self updateController] start];
...
Here’s the UpdateController initializer
- (id) initWithRootDetailViewController:(FFRootDetailViewController*)rdvc
{
if (self = [super init])
{
_rootDetailViewController = rdvc;
_updateQueue = [[NSOperationQueue alloc] init];
}
return self;
}
Here’s UpdateController start
- (void) start
{
//sweep once a minute for updates
[self setTimer:[NSTimer scheduledTimerWithTimeInterval:60.0 target:self selector:#selector(sweepForUpdates:) userInfo:nil repeats:YES]];
}
Here is sweepForUpdates, the selector called by the timer:
- (void) sweepForUpdates:(NSTimer*)timer
{
FormHeader* fh;
NSInvocationOperation* op;
NSInteger sectionIdx = [[self dataController] sectionIndexForFormTypeWithTitle:SFTShares];
NSInteger headerCount = [[self dataController] numberOfRowsInSection:sectionIdx];
NSArray* changed;
NSMutableDictionary* params;
NSLog(#"Notice - sweepForUpdates(1) called:");
for (NSInteger i = 0; i < headerCount; i++)
{
fh = [[self dataController] formHeaderAtIndexPath:[NSIndexPath indexPathForRow:i inSection:sectionIdx]];
changed = [[self dataController] formDatasModifiedSince:[fh modifiedAt] ForFormHeader:fh];
if ([changed count])
{
NSLog(#"Error - sweepForUpdates(2) update: changes to update found");
params = [[NSMutableDictionary alloc] init];
[params setObject:fh forKey:#"formHeader"];
[params setObject:[self rootDetailViewController] forKey:#"rootDetailViewController"];
op = [[NSInvocationOperation alloc] initWithTarget:[FFParseController sharedInstance] selector:#selector(updateRemoteForm:) object:params];
if ([[[self updateQueue] operations] count])
{
[op addDependency:[[[self updateQueue] operations] lastObject]];
}
[[self updateQueue] addOperation:op];
}
else
{
NSLog(#"Error - sweepForUpdates(3) save: no changes found");
}
}
NSLog(#"Notice - sweepForUpdates(4) done:");
}
In this case there are 2 objects to examine for updates. Here is the console output for 1 sweep:
2015-02-16 09:22:28.569 formogen[683:806645] Notice - sweepForUpdates(1) called:
2015-02-16 09:22:28.580 formogen[683:806645] Error - sweepForUpdates(3) save: no changes found
2015-02-16 09:22:28.583 formogen[683:806645] Error - sweepForUpdates(3) save: no changes found
2015-02-16 09:22:28.584 formogen[683:806645] Notice - sweepForUpdates(4) done:
2015-02-16 09:22:29.249 formogen[683:806645] Notice - sweepForUpdates(1) called:
2015-02-16 09:22:29.254 formogen[683:806645] Error - sweepForUpdates(3) save: no changes found
2015-02-16 09:22:29.256 formogen[683:806645] Error - sweepForUpdates(3) save: no changes found
2015-02-16 09:22:29.256 formogen[683:806645] Notice - sweepForUpdates(4) done:
Neither object has updates, which is correct. But I do not understand why the selector is called twice.
Thanks
Add logging to start. You probably call it more than once.
Note that UpdateController can never deallocate, because the timer is retaining it. That may be ok, but keep that in mind if you believe you're deallocating it (and its timer).
I am new to xcode iOS programming and I really need your help with the error of unrecognised selector sent to instance. Below is my implementation
//.h file
#interface AppAccountController : UIViewController <UIAlertViewDelegate>{
IBOutlet UITextField *txtName;
IBOutlet UITextField *txtEmail;
IBOutlet UIDatePicker *dateOB;
IBOutlet UITextField *txtPwd;
IBOutlet UITextField *txtRePwd;
}
- (IBAction)btnSave:(id)sender;
#end
//.m file
#interface AppAccountController ()
#property (strong, nonatomic) IBOutlet UITextField *txtName;
#property (strong, nonatomic) IBOutlet UITextField *txtEmail;
#property (strong, nonatomic) IBOutlet UIDatePicker *dateOB;
#property (strong, nonatomic) IBOutlet UITextField *txtPwd;
#property (strong, nonatomic) IBOutlet UITextField *txtRePwd;
#property (strong, nonatomic) IBOutlet UIButton *btnSave;
#end
#implementation AppAccountController
- (void)viewDidLoad
{
[super viewDidLoad];
}
- (IBAction)btnSave:(id)sender {
// Create strings to store the text info
NSString *name = [txtName text];
NSString *email = [txtEmail text];
NSDate *dob = [dateOB date];
NSString *passwd = [txtPwd text];
NSString *repasswd = [txtRePwd text];
if ([txtName.text isEqualToString:#""])
{
[txtName becomeFirstResponder];
UIAlertView* finalCheck = [[UIAlertView alloc]
initWithTitle:#"Attention"
message:#"Name Field is empty. Please enter a name"
delegate:self
cancelButtonTitle:#"OK"
otherButtonTitles:nil] ;
[finalCheck show];
finalCheck.tag = 1;
}
else if ([txtEmail.text isEqualToString:#""])
{
[txtEmail becomeFirstResponder];
UIAlertView* finalCheck = [[UIAlertView alloc]
initWithTitle:#"Attention"
message:#"Email Field is empty. Please enter an Email"
delegate:self
cancelButtonTitle:#"OK"
otherButtonTitles:nil] ;
[finalCheck show];
finalCheck.tag = 2;
}
else if ([txtPwd.text isEqualToString:#""])
{
[txtPwd becomeFirstResponder];
UIAlertView* finalCheck = [[UIAlertView alloc]
initWithTitle:#"Attention"
message:#"Password Field is empty. Please enter a Password"
delegate:self
cancelButtonTitle:#"OK"
otherButtonTitles:nil] ;
[finalCheck show];
finalCheck.tag = 3;
}
else if ([txtRePwd.text isEqualToString:#""])
{
[txtRePwd becomeFirstResponder];
UIAlertView* finalCheck = [[UIAlertView alloc]
initWithTitle:#"Attention"
message:#"Confirm Password Field is empty. Please enter to confirm Password"
delegate:self
cancelButtonTitle:#"OK"
otherButtonTitles:nil] ;
[finalCheck show];
finalCheck.tag = 4;
}
else if (![passwd isEqualToString:repasswd])
{
[txtPwd becomeFirstResponder];
UIAlertView* finalCheck = [[UIAlertView alloc]
initWithTitle:#"Attention"
message:#"Passwords do not match. Please enter to confirm Password"
delegate:self
cancelButtonTitle:#"OK"
otherButtonTitles:nil] ;
[finalCheck show];
finalCheck.tag = 5;
}
else{
// Store the data
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:name forKey:#"name"];
[defaults setObject:email forKey:#"email"];
[defaults setObject:passwd forKey:#"passwd"];
[defaults setObject:dob forKey:#"dob"];
[defaults synchronize];
NSLog(#"Data saved");
[self performSegueWithIdentifier:#"saveLoad" sender:self];
}
}
- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex {
if(alertView.tag == 1) {
[txtName becomeFirstResponder];
NSLog(#"Name AlertView is clicked");
}
else if(alertView.tag == 2) {
[txtEmail becomeFirstResponder];
NSLog(#"Email AlertView is clicked");
}
else if(alertView.tag == 3) {
[txtPwd becomeFirstResponder];
NSLog(#"Password AlertView is clicked");
}
else if(alertView.tag == 4) {
[txtRePwd becomeFirstResponder];
NSLog(#"Re-Password AlertView is clicked");
}
else if(alertView.tag == 5) {
[txtRePwd becomeFirstResponder];
NSLog(#"Re-Password AlertView is clicked");
}
}
#end
Whenever I press the btnSave button or after moving on to another UI element (one element gaining focus after another one losses it), I get unrecognised selector sent to instance.
UPDATE
2014-12-18 18:06:23.794 XPB[964:35743] -[AppAccountController btnSave:forEvent:]: unrecognized selector sent to instance 0x7fd949d923f0
2014-12-18 18:06:23.834 XPB[964:35743] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[AppAccountController btnSave:forEvent:]: unrecognized selector sent to instance 0x7fd949d923f0'
*** First throw call stack:
(
0 CoreFoundation 0x000000010bd2df35 __exceptionPreprocess + 165
1 libobjc.A.dylib 0x000000010a182bb7 objc_exception_throw + 45
2 CoreFoundation 0x000000010bd3504d -[NSObject(NSObject) doesNotRecognizeSelector:] + 205
3 CoreFoundation 0x000000010bc8d27c ___forwarding___ + 988
4 CoreFoundation 0x000000010bc8ce18 _CF_forwarding_prep_0 + 120
5 UIKit 0x00000001087e98be -[UIApplication sendAction:to:from:forEvent:] + 75
6 UIKit 0x00000001088f0410 -[UIControl _sendActionsForEvents:withEvent:] + 467
7 UIKit 0x00000001088ef7df -[UIControl touchesEnded:withEvent:] + 522
8 UIKit 0x000000010882f308 -[UIWindow _sendTouchesForEvent:] + 735
9 UIKit 0x000000010882fc33 -[UIWindow sendEvent:] + 683
10 UIKit 0x00000001087fc9b1 -[UIApplication sendEvent:] + 246
11 UIKit 0x0000000108809a7d _UIApplicationHandleEventFromQueueEvent + 17370
12 UIKit 0x00000001087e5103 _UIApplicationHandleEventQueue + 1961
13 CoreFoundation 0x000000010bc63551 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17
14 CoreFoundation 0x000000010bc5941d __CFRunLoopDoSources0 + 269
15 CoreFoundation 0x000000010bc58a54 __CFRunLoopRun + 868
16 CoreFoundation 0x000000010bc58486 CFRunLoopRunSpecific + 470
17 GraphicsServices 0x000000010c64e9f0 GSEventRunModal + 161
18 UIKit 0x00000001087e8420 UIApplicationMain + 1282
19 XPB 0x00000001083965b3 main + 115
20 libdyld.dylib 0x000000010ce4b145 start + 1
21 ??? 0x0000000000000001 0x0 + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException
The problem is that btnSave: and btnSave:forEvent: are two different things.
Your AppAccountController has a btnSave: method, so it can be sent the btnSave: message. But it has no btnSave:forEvent: method. Unfortunately, that's the selector you specified to be used whenever the button is pressed. The button is trying to send btnSave:forEvent: to your AppAccountController, and AppAccountController doesn't know what to do with that. So it crashes.
The easiest solution is to fix the button so that it sends btnSave: and not btnSave:forEvent:. (You will almost never need the two-parameter variant of an action method in any case.)
I test an app that uses a custom UIFont. This font is used in a UILabel that is zoomable -- it has a CATiledLayer layer.
This is the code for the UILabel class:
#import "ZoomableLabel.h"
#implementation ZoomableLabel
+ (Class)layerClass
{
return [CATiledLayer class];
}
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
[self setupView];
}
return self;
}
//- (id)initWithCoder:(NSCoder *)aDecoder {
// self = [super initWithCoder:aDecoder];
// if (self) {
// // Initialization code
// [self setupView];
// }
// return self;
//}
-(void)awakeFromNib {
[super awakeFromNib];
[self setupView];
}
- (void)setupView {
CATiledLayer *layerForView = (CATiledLayer *)self.layer;
layerForView.levelsOfDetailBias = 3;
layerForView.levelsOfDetail = 1;
}
-(void)setText:(NSString *)value {
self.layer.contents = nil;
[super setText:value];
[self setNeedsDisplay];
}
-(void)setTextColor:(UIColor *)value {
self.layer.contents = nil;
[super setTextColor:value];
[self setNeedsDisplay];
}
#end
When I run the app on a device or the simulator for the first time (that is the app is installed for the first time on the device or the simulator) I get a crash. Then this crash never happens again! UPDATE: The crash happens VERY randomly (especially when demoing the app...) but not only the first time. This is all the info I managed to get from XCode:
Thread 3 name: Dispatch queue: com.apple.root.default-priority
Thread 3 Crashed: 0 WebCore 0x333adbfa WTF::HashTable<WebCore::FontData const*, std::__1::pair<WebCore::FontData const*, WebCore::GlyphPageTreeNode*>, WTF::PairFirstExtractor<std::__1::pair<WebCore::FontData const*, WebCore::GlyphPageTreeNode*> >, WTF::PtrHash<WebCore::FontData const*>, WTF::HashMapValueTraits<WTF::HashTraits<WebCore::FontData const*>, WTF::HashTraits<WebCore::GlyphPageTreeNode*> >, WTF::HashTraits<WebCore::FontData const*> >::rehash(int) + 42
1 WebCore 0x333adcd4 WTF::HashTableAddResult<WTF::HashTableIterator<WebCore::FontData const*, std::__1::pair<WebCore::FontData const*, WebCore::GlyphPageTreeNode*>, WTF::PairFirstExtractor<std::__1::pair<WebCore::FontData const*, WebCore::GlyphPageTreeNode*> >, WTF::PtrHash<WebCore::FontData const*>, WTF::HashMapValueTraits<WTF::HashTraits<WebCore::FontData const*>, WTF::HashTraits<WebCore::GlyphPageTreeNode*> >, WTF::HashTraits<WebCore::FontData const*> > > WTF::HashTable<WebCore::FontData const*, std::__1::pair<WebCore::FontData const*, WebCore::GlyphPageTreeNode*>, WTF::PairFirstExtractor<std::__1::pair<WebCore::FontData const*, WebCore::GlyphPageTreeNode*> >, WTF::PtrHash<WebCore::FontData const*>, WTF::HashMapValueTraits<WTF::HashTraits<WebCore::FontData const*>, WTF::HashTraits<WebCore::GlyphPageTreeNode*> >, WTF::HashTraits<WebCore::FontData const*> >::add<WTF::HashMapTranslator<WTF::HashMapValueTraits<WTF::HashTraits<WebCore::FontData const*>, WTF::HashTraits<WebCore::GlyphPageTreeNode*> >, WTF::PtrHash<WebCore::FontData const*> >, WebCore::FontData const*, WebCore::GlyphPageTreeNode*>(WebCore::FontData const* const&, WebCore::GlyphPageTreeNode* const&) + 56
2 WebCore 0x333a5cac WebCore::GlyphPageTreeNode::getChild(WebCore::FontData const*, unsigned int) + 264
3 WebCore 0x333a55d8 WebCore::Font::glyphDataAndPageForCharacter(int, bool, WebCore::FontDataVariant) const + 528
4 WebCore 0x333a53b6 WebCore::Font::glyphDataForCharacter(int, bool, WebCore::FontDataVariant) const + 18
5 WebCore 0x333a4b36 WebCore::WidthIterator::advance(int, WebCore::GlyphBuffer*) + 398
6 WebCore 0x333a4794 WebCore::Font::floatWidthForSimpleText(WebCore::TextRun const&, WebCore::GlyphBuffer*, WTF::HashSet<WebCore::SimpleFontData const*, WTF::PtrHash<WebCore::SimpleFontData const*>, WTF::HashTraits<WebCore::SimpleFontData const*> >*, WebCore::GlyphOverflow*) const + 60
7 WebCore 0x333a4546 WebCore::Font::width(WebCore::TextRun const&, WTF::HashSet<WebCore::SimpleFontData const*, WTF::PtrHash<WebCore::SimpleFontData const*>, WTF::HashTraits<WebCore::SimpleFontData const*> >*, WebCore::GlyphOverflow*) const + 250
8 WebCore 0x333a60e0 WebCore::truncateString(WTF::String const&, float, WebCore::Font const&, unsigned int (*)(WTF::String const&, unsigned int, unsigned int, unsigned short*, bool), bool, float*, bool, float, bool) + 296
9 WebCore 0x333a5fac WebCore::StringTruncator::rightTruncate(WTF::String const&, float, WebCore::Font const&, WebCore::StringTruncator::EnableRoundingHacksOrNot, float&, bool, float) + 60
10 WebKit 0x375fc718 applyEllipsisStyle(WTF::String const&, WebEllipsisStyle, float, WebCore::Font const&, WebCore::StringTruncator::EnableRoundingHacksOrNot, float*, bool, float, bool) + 464
11 WebKit 0x375ff3a8 -[NSString(WebStringDrawing) __web_drawInRect:withFont:ellipsis:alignment:letterSpacing:lineSpacing:includeEmoji:truncationRect:measureOnly:renderedStringOut:drawUnderline:] + 5036
12 WebKit 0x375fdfe8 -[NSString(WebStringDrawing) __web_drawInRect:withFont:ellipsis:alignment:letterSpacing:lineSpacing:includeEmoji:truncationRect:measureOnly:renderedStringOut:] + 112
13 WebKit 0x375fdf64 -[NSString(WebStringDrawing) __web_drawInRect:withFont:ellipsis:alignment:letterSpacing:lineSpacing:includeEmoji:truncationRect:measureOnly:] + 108
14 WebKit 0x375fdee4 -[NSString(WebStringDrawing) _web_drawInRect:withFont:ellipsis:alignment:lineSpacing:includeEmoji:truncationRect:measureOnly:] + 108
15 WebKit 0x375fde64 -[NSString(WebStringDrawing) _web_sizeInRect:withFont:ellipsis:lineSpacing:] + 80
16 UIKit 0x353698c2 -[NSString(UIStringDrawing) sizeWithFont:constrainedToSize:lineBreakMode:lineSpacing:] + 122
17 UIKit 0x3535daa6 -[UILabel _legacy_drawTextInRect:baselineCalculationOnly:] + 594
18 UIKit 0x35321c5a -[UILabel _drawTextInRect:baselineCalculationOnly:] + 162
19 UIKit 0x35320a26 -[UILabel drawTextInRect:] + 446
20 UIKit 0x35320860 -[UILabel drawRect:] + 68
21 UIKit 0x3531fd20 -[UIView(CALayerDelegate) drawLayer:inContext:] + 360
22 QuartzCore 0x37b84bb8 -[CALayer drawInContext:] + 108
23 QuartzCore 0x37c62624 tiled_layer_render(_CAImageProvider*, unsigned int, unsigned int, unsigned int, unsigned int, void*) + 1416
24 QuartzCore 0x37bd755c CAImageProviderThread(unsigned int*, bool) + 508
25 libdispatch.dylib 0x37b5e95c _dispatch_root_queue_drain + 248
26 libdispatch.dylib 0x37b5eabc _dispatch_worker_thread2 + 80
27 libsystem_c.dylib 0x38862a0e _pthread_wqthread + 358
28 libsystem_c.dylib 0x388628a0 start_wqthread + 4
Does anybody has any idea on this? I think this crash does not happen on an iOS 5 device...
Here is my take on Summon's answer which uses Core Text to address issues with languages other than English (although I have only tested Swedish) and odd font symbols. Note that it uses initWithCoder as I'm using StoryBoard. It also shows how to center the text in the label horizontally (had less luck with vertical where I simply had to test my way to the right placement).
Header:
#import <UIKit/UIKit.h>
#import <QuartzCore/QuartzCore.h>
#interface DNLabel : UILabel
#end
Implementation:
#import "DNLabel.h"
#import <CoreText/CoreText.h>
#implementation DNLabel
+ (Class)layerClass
{
return [CATiledLayer class];
}
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self)
{
[self setupView];
}
return self;
}
-(void)awakeFromNib
{
[super awakeFromNib];
[self setupView];
}
- (void)setupView {
CATiledLayer *layerForView = (CATiledLayer *)self.layer;
layerForView.levelsOfDetailBias = 10;
layerForView.levelsOfDetail = 10;
}
// LEAVE IT EMPTY
-(void)drawRect:(CGRect)r
{
// UIView uses the existence of -drawRect: to determine if should allow its CALayer
// to be invalidated, which would then lead to the layer creating a backing store and
// -drawLayer:inContext: being called.
// By implementing an empty -drawRect: method, we allow UIKit to continue to implement
// this logic, while doing our real drawing work inside of -drawLayer:inContext:
}
// These calls inside this method are thread SAFE
-(void)drawLayer:(CALayer*)layer inContext:(CGContextRef)context
{
// Core Text version
CTFontRef ctFont = CTFontCreateWithName((__bridge CFStringRef)self.font.fontName,
self.font.pointSize,
NULL);
// set color
CGColorRef color = [[[UIColor blackColor] colorWithAlphaComponent:0.75f] CGColor];
// pack it into attributes dictionary
NSDictionary *attributesDict = [NSDictionary dictionaryWithObjectsAndKeys:
(id)CFBridgingRelease(ctFont), (id)kCTFontAttributeName,
color, (id)kCTForegroundColorAttributeName,
nil, (id)kCTUnderlineStyleAttributeName, nil];
// make the attributed string
NSAttributedString *stringToDraw = [[NSAttributedString alloc] initWithString:self.text
attributes:attributesDict];
// flip the coordinate system
CGContextSetTextMatrix(context, CGAffineTransformIdentity);
CGContextTranslateCTM(context, 0, self.bounds.size.height);
CGContextScaleCTM(context, 1.0, -1.0);
// check size of text and set position centered horizontally
CGSize size = [self.text sizeWithFont:self.font];
CGContextSetTextPosition(context, self.bounds.size.width/2 - size.width/2, 1);
// draw
CTLineRef line = CTLineCreateWithAttributedString(
(CFAttributedStringRef)CFBridgingRetain(stringToDraw));
CTLineDraw(line, context);
}
#end
These are the .h and .m classes of a zoomable label based on CATiledLayer that never crashes:
Header:
#import <QuartzCore/QuartzCore.h>
#interface ZoomableLabel : UILabel {
CGPoint textDrawPoint;
CGPoint shadowDrawPoint;
}
#end
Implementation:
#import "ZoomableLabel.h"
#implementation ZoomableLabel
+ (Class)layerClass
{
return [CATiledLayer class];
}
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
[self setupView];
}
return self;
}
-(void)awakeFromNib {
[super awakeFromNib];
[self setupView];
}
- (void)setupView {
CATiledLayer *layerForView = (CATiledLayer *)self.layer;
layerForView.levelsOfDetailBias = 3;
layerForView.levelsOfDetail = 1;
textDrawPoint = CGPointMake(11, -22);
shadowDrawPoint = CGPointMake(10, -23);
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
textDrawPoint = CGPointMake(11, -42);
shadowDrawPoint = CGPointMake(8, -46);
}
}
// DO NOT USER THESE METHODS ANYMORE
//-(void)setText:(NSString *)value {
//// self.layer.contents = nil;
// [super setText:value];
// [self setNeedsDisplayInRect:self.bounds];
//}
//
//-(void)setTextColor:(UIColor *)value {
//// self.layer.contents = nil;
// [super setTextColor:value];
// [self setNeedsDisplayInRect:self.bounds];
//}
// LEAVE IT EMPTY
-(void)drawRect:(CGRect)r
{
// UIView uses the existence of -drawRect: to determine if should allow its CALayer
// to be invalidated, which would then lead to the layer creating a backing store and
// -drawLayer:inContext: being called.
// By implementing an empty -drawRect: method, we allow UIKit to continue to implement
// this logic, while doing our real drawing work inside of -drawLayer:inContext:
}
// These calls inside this method are thread SAFE
-(void)drawLayer:(CALayer*)layer inContext:(CGContextRef)context
{
// Do all your drawing here. Do not use UIGraphics to do any drawing, use Core Graphics instead.
CGContextScaleCTM(context, 1.0f, -1.0f);
CGContextSelectFont(context, [self.font.fontName UTF8String], self.font.pointSize, kCGEncodingMacRoman);
CGContextSetTextDrawingMode(context, kCGTextFill);
// CGContextSetShouldAntialias(context, true);
CGContextSetFillColorWithColor(context, [[[UIColor blackColor] colorWithAlphaComponent:0.75f] CGColor]);
CGContextShowTextAtPoint(context, shadowDrawPoint.x, shadowDrawPoint.y, [self.text UTF8String], self.text.length);
CGContextSetFillColorWithColor(context, [self.textColor CGColor]);
CGContextShowTextAtPoint(context, textDrawPoint.x, textDrawPoint.y, [self.text UTF8String], self.text.length);
}
#end
In my COCOA application I have implemented a custom borderless window. The content area of the Window is fully covered by a WebView. I want this borderless window to move when user clicks and drag mouse anywhere in the content area. I tried by overriding isMovableByWindowBackground but no use. How can I fix this problem?
Calling -setMovableByWindowBackround:YES on the WebView and making the window textured might work.
This is how I did it.
#import "BorderlessWindow.h"
#implementation BorderlessWindow
#synthesize initialLocation;
- (id)initWithContentRect:(NSRect)contentRect
styleMask:(NSUInteger)windowStyle
backing:(NSBackingStoreType)bufferingType
defer:(BOOL)deferCreation
{
if((self = [super initWithContentRect:contentRect
styleMask:NSBorderlessWindowMask
backing:NSBackingStoreBuffered
defer:NO]))
{
return self;
}
return nil;
}
- (BOOL) canBecomeKeyWindow
{
return YES;
}
- (BOOL) acceptsFirstResponder
{
return YES;
}
- (NSTimeInterval)animationResizeTime:(NSRect)newWindowFrame
{
return 0.1;
}
- (void)sendEvent:(NSEvent *)theEvent
{
if([theEvent type] == NSKeyDown)
{
if([theEvent keyCode] == 36)
return;
}
if([theEvent type] == NSLeftMouseDown)
[self mouseDown:theEvent];
else if([theEvent type] == NSLeftMouseDragged)
[self mouseDragged:theEvent];
[super sendEvent:theEvent];
}
- (void)mouseDown:(NSEvent *)theEvent
{
self.initialLocation = [theEvent locationInWindow];
}
- (void)mouseDragged:(NSEvent *)theEvent
{
NSRect screenVisibleFrame = [[NSScreen mainScreen] visibleFrame];
NSRect windowFrame = [self frame];
NSPoint newOrigin = windowFrame.origin;
NSPoint currentLocation = [theEvent locationInWindow];
if(initialLocation.y > windowFrame.size.height - 40)
{
newOrigin.x += (currentLocation.x - initialLocation.x);
newOrigin.y += (currentLocation.y - initialLocation.y);
if ((newOrigin.y + windowFrame.size.height) > (screenVisibleFrame.origin.y + screenVisibleFrame.size.height))
{
newOrigin.y = screenVisibleFrame.origin.y + (screenVisibleFrame.size.height - windowFrame.size.height);
}
[self setFrameOrigin:newOrigin];
}
}
#end
And .h file:
#import <Cocoa/Cocoa.h>
#interface BorderlessWindow : NSWindow {
NSPoint initialLocation;
}
- (id)initWithContentRect:(NSRect)contentRect
styleMask:(NSUInteger)windowStyle
backing:(NSBackingStoreType)bufferingType
defer:(BOOL)deferCreation;
#property (assign) NSPoint initialLocation;
#end
Since this is the top hit on Google...the provided approach didn't work for me as WKWebView intercepts the mouse events before they reach the window. I had to instead create a subclass of WKWebView and do the work there (h/t to Apple's Photo Editor/WindowDraggableButton.swift example).
I use Xamarin, but the code is pretty simple...here are the important bits:
// How far from the top of the window you are allowed to grab the window
// to begin the drag...the title bar height, basically
public Int32 DraggableAreaHeight { get; set; } = 28;
public override void MouseDown(NSEvent theEvent)
{
base.MouseDown(theEvent);
var clickLocation = theEvent.LocationInWindow;
var windowHeight = Window.Frame.Height;
if (clickLocation.Y > (windowHeight - DraggableAreaHeight))
_dragShouldRepositionWindow = true;
}
public override void MouseUp(NSEvent theEvent)
{
base.MouseUp(theEvent);
_dragShouldRepositionWindow = false;
}
public override void MouseDragged(NSEvent theEvent)
{
base.MouseDragged(theEvent);
if (_dragShouldRepositionWindow)
{
this.Window.PerformWindowDrag(theEvent);
}
}
#starkos porvided the correct answer at https://stackoverflow.com/a/54987061/140927 The following is just the ObjC implementation in a subclass of WKWebView:
BOOL _dragShouldRepositionWindow = NO;
- (void)mouseDown:(NSEvent *)event {
[super mouseDown:event];
NSPoint loc = event.locationInWindow;
CGFloat height = self.window.frame.size.height;
if (loc.y > height - 28) {
_dragShouldRepositionWindow = YES;
}
}
- (void)mouseUp:(NSEvent *)event {
[super mouseUp:event];
_dragShouldRepositionWindow = NO;
}
- (void)mouseDragged:(NSEvent *)event {
[super mouseDragged:event];
if (_dragShouldRepositionWindow) {
[self.window performWindowDragWithEvent:event];
}
}
For further info about how to manipulate the title bar, see https://github.com/lukakerr/NSWindowStyles
I wrote code like:
+ (CGFloat)tableView:(UITableView*)tableView rowHeightForObject:(id)item {
CustomTTTableSubtitleItem* captionedItem = item;
CGFloat maxWidth = tableView.width - kHPadding*2;
CGSize titleSize = [captionedItem.title sizeWithFont:TTSTYLEVAR(myTitleFont) constrainedToSize:CGSizeMake(maxWidth, CGFLOAT_MAX) lineBreakMode:UILineBreakModeWordWrap];
}
Got this exception:
2011-07-24 03:10:18.762 xinyou[15941:b303] -[TTDefaultStyleSheet
myTitleFont]: unrecognized selector sent to instance 0x5b5e120
2011-07-24 03:10:18.765 xinyou[15941:b303] * Terminating app due to
uncaught exception 'NSInvalidArgumentException', reason:
'-[TTDefaultStyleSheet myTitleFont]: unrecognized selector sent to
instance 0x5b5e120'
* Call stack at first throw: ( 0 CoreFoundation
0x0119a5a9 exceptionPreprocess + 185 1 libobjc.A.dylib
0x012ee313 objc_exception_throw + 44 2 CoreFoundation
0x0119c0bb -[NSObject(NSObject) doesNotRecognizeSelector:] + 187 3
CoreFoundation 0x0110b966 __forwarding + 966
4 CoreFoundation 0x0110b522
_CF_forwarding_prep_0 + 50 5 xinyou
0x000081f9 +[CustomTTTableSubtitleItemCell
tableView:rowHeightForObject:] + 186 6 xinyou
0x000a6c92 -[TTTableViewVarHeightDelegate
tableView:heightForRowAtIndexPath:] + 156 7 UIKit
0x0064a6d5 -[UISectionRowData
In this exception you can see [TTDefaultStyleSheet myTitleFont]: unrecognized selector sent to instance 0x5b5e120 but actually myTitleFont defined in XYDefaultStyleSheet and I've imported XYDefaultStyleSheet.h in my class. XYDefaultStyleSheet.h and XYDefaultStyleSheet.m are like:
XYDefaultStyleSheet.h
#import "Three20/Three20.h"
#interface XYDefaultStyleSheet : TTDefaultStyleSheet
#property(nonatomic,readonly) UIColor* myHeadingColor;
#property(nonatomic,readonly) UIColor* mySubtextColor;
#property(nonatomic,readonly) UIColor* myTitleColor;
#property(nonatomic,readonly) UIFont* myTitleFont;
#property(nonatomic,readonly) UIFont* myHeadingFont;
#property(nonatomic,readonly) UIFont* mySubtextFont;
#end
XYDefaultStyleSheet.m
#import "XYDefaultStyleSheet.h"
///////////////////////////////////////////////////////////////////////////////////////////////////
#implementation XYDefaultStyleSheet
///////////////////////////////////////////////////////////////////////////////////////////////////
// styles
///////////////////////////////////////////////////////////////////////////////////////////////////
// public colors
- (UIColor*)myTitleColor {
return [UIColor blackColor];
}
- (UIColor*)myHeadingColor {
return RGBCOLOR(80, 110, 140);
}
- (UIColor*)mySubtextColor {
return [UIColor grayColor];
}
///////////////////////////////////////////////////////////////////////////////////////////////////
// public fonts
- (UIFont*)myTitleFont {
return [UIFont boldSystemFontOfSize:16];
}
- (UIFont*)myHeadingFont {
return [UIFont boldSystemFontOfSize:13];
}
- (UIFont*)mySubtextFont {
return [UIFont systemFontOfSize:12];
}
#end
why always tell [TTDefaultStyleSheet myTitleFont] ... if the problem is really myTitleFont, it should be [XYDefaultStyleSheet myTitleFont], why TTDefaultStyleSheet?
got it! Init my style sheet in AppDelegate.
This is an answer to #Jason Zhao's answer about initialising within the AppDelegate.
- (void)applicationDidFinishLaunching:(UIApplication *)application {
// Override point for customization after app launch
[TTStyleSheet setGlobalStyleSheet:[[[CustomDefaultStyleSheet alloc]
init] autorelease]];
[window addSubview:[navigationController view]];
[window makeKeyAndVisible];
}
Original code is from here, which has a lot of useful information about using TTStyleSheet's:
Three20 Stylesheets iPhone Tutorial