I have written a subclass of UIControl that tracks a number of gestures that are of interest to me. In the documentation for the UIControlEvents enumeration, it says that there is a range of event numbers called UIControlEventApplicationReserved that is "available for application use." Does this mean that I am free to use this range of numbers for my own custom events?
If so, can someone please tell me how to fire events? The obvious way I can think of to do it is this:
enum {
...
MyCustomEvent = 65,
...
};
...
UIEvent* customEvent;
...
for (id target in [self allTargets])
{
for (NSString* action in [self actionsForTarget:target forControlEvent:MyCustomEvent])
{
[self sendAction:NSSelectorFromString(action) to:target forEvent:customEvent];
}
}
Would that even work?
Okay, this is an old subject but I'm going to add my answer to this. I can't really tell for sure whether you can use this mask for your own application even though I suspect it.
But I can tell you for sure how to use it. For starter this value masks the bits at position 24, 25, 26 and 27. You should write an enum of your own that uses this bits only, for example:
enum {
MyPrimaryActionEvent = 1 << 24,
MySecondaryActionEvent = 1 << 25,
};
Once that is done you can register for these actions:
[myButton addTarget:self action:#selector(someAction:) forControlEvents: MyPrimaryActionEvent];
Every time the action MyPrimaryActionEvent is triggered, self will receive the message someAction:. Now how to trigger that action is up to the button itself. In your own UIControl subclass you can trigger the change as follow:
[self sendActionsForControlEvents:MyPrimaryActionEvent];
This will send all the actions to all the targets registered for MyPrimaryActionEvent event. And you're done.
Related
I am trying to intercept mouse move events using the CGEvent.tapCreate(tap:place:options:eventsOfInterest:callback:userInfo:) method as shown below:
let cfMachPort = CGEvent.tapCreate(tap: CGEventTapLocation.cghidEventTap,
place: CGEventTapPlacement.headInsertEventTap,
options: CGEventTapOptions.defaultTap,
eventsOfInterest:CGEventMask(CGEventType.mouseMoved.rawValue),
callback: {(eventTapProxy, eventType, event, mutablePointer) -> Unmanaged<CGEvent>? in event
print(event.type.rawValue) //Breakpoint
return nil
}, userInfo: nil)
let runloopSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, cfMachPort!, 0)
let runLoop = RunLoop.current
let cfRunLoop = runLoop.getCFRunLoop()
CFRunLoopAddSource(cfRunLoop, runloopSource, CFRunLoopMode.defaultMode)
I pass as event type eventsOfInterest mouseMoved events with a raw value of 5 as seen in the documentation. But for some reason my print() is not executed unless I click with the mouse. Inspecting the send mouse event in the debugger gives me a raw value of 2, which according to the documentation is a leftMouseUp event.
In the documentation for CGEvent.tapCreate(tap:place:options:eventsOfInterest:callback:userInfo:) it says:
Event taps receive key up and key down events [...]
So it seems like the method ignores mouseMoved events in general?! But how am I supposed to listen to mouseMoved events? I am trying to prevent my cursor (custom cursor) from being replaced (for example when I hover over the application dock at the bottom of the screen).
You need to bitshift the CGEventType value used to create the CGEventMask parameter. In Objective-C, there is a macro to do this: CGEventMaskBit.
From the CGEventMask documentation:
to form the bit mask, use the CGEventMaskBit macro to convert each constant into an event mask and then OR the individual masks together
I don't know the equivalent mechanism in swift; but the macro itself looks like this:
*/ #define CGEventMaskBit(eventType) ((CGEventMask)1 << (eventType))
In your example, it's sufficient to just manually shift the argument; e.g.
eventsOfInterest:CGEventMask(1 << CGEventType.mouseMoved.rawValue),
I would point out that the code example given in the question is a little dangerous; as it creates a default event tap and then drops the events rather than allowing them to be processed. This messes up mouse click handling and it was tricky to actually terminate the application using the mouse. Anyone running the example could set the event tap type to CGEventTapOptions.listenOnly to prevent that.
Here is a way to listen for mouseMove global events (tested with Xcode 11.2+, macOS 10.15)
// ... say in AppDelegate
var globalObserver: Any!
var localObserver: Any!
func applicationDidFinishLaunching(_ aNotification: Notification) {
globalObserver = NSEvent.addGlobalMonitorForEvents(matching: .mouseMoved) { event in
let location = event.locationInWindow
print("in background: {\(location.x), \(location.y)}")
}
localObserver = NSEvent.addLocalMonitorForEvents(matching: .mouseMoved) { event in
let location = event.locationInWindow
print("active: {\(location.x), \(location.y)}")
return event
}
...
There's another thing incorrect in your code, although you might be lucky and it isn't normally causing a problem.
As documented for the mode parameter to CFRunLoopAddSource: "Use the constant kCFRunLoopCommonModes to add source to the set of objects monitored by all the common modes."
That third parameter should instead be CFRunLoopMode.commonModes.
What you have, CFRunLoopMode.defaultMode aka kCFRunLoopDefaultMode, is instead for use when calling CFRunLoopRun.
I have created a bar over the keyboard for textfields with previous/next/done button selections. In doing so, I noticed an odd occurance with my tags that I used to navigate between the textfields. I am creating my interface programmatically with a loop, and as such, just set the tag values to the loop variable i.
I started the i variable at 0 so the very first text field created had a tag of zero. Basically what was happening is the 'previous' button functionality would only go so low as 1. It wouldn't even go back to the text field with the 0 tag. The only way to fix this was to increase all tag values by 1 so the first text field started at 1 instead of zero.
Here is my code. Is there a bug in my code that I cannot see? or is this a weird issue with tags?
-(void)gotoPrevTextfield{
// If the active textfield is the first one, can't go to any previous
// field so just return.
UITextField *textField = (UITextField *)[inputsView viewWithTag:0];
NSLog(#"%i",textField.tag);
NSLog(#"%i",txtActiveField.tag);
if (txtActiveField == textField) {
NSLog(#"returning at previous");
return;
}
else {
NSLog(#"set responder");
// Otherwise if a different textfield has the focus, the operation
// of "previous" button can be done and set the previous as the first
// responder.
textField = (UITextField *)[inputsView viewWithTag:txtActiveField.tag - 1];
NSLog(#"%i",textField.tag);
NSLog(#"%i",txtActiveField.tag);
[textField becomeFirstResponder];
}
}
Note that unset tags default to 0 so that is almost a poor choice. You may be getting another view that you don't expect.
A fairly good practice is to add some constant such as 100, consider making the constant a const int or #define for clarity.
I have a button that's created via a NIB file. I've derived a class from UIButton, replaced the class name in the NIB file.
Now my button displays with no background. The text is there, the text font and color are right, and it reacts to taps as expected, but it's as if the background is transparent. In the NIB, it's not transparent - I did not change any of the properties other than the class name.
The subclass is trivial - it overrides nothing (for now). Please, what am I doing wrong?
The reason I need a subclass of UIButton is because I want to be able, under certain circumstances, to drag text from the button to elsewhere. If there's an alternative way to handle drag and drop in a UIKit provided view, I'm willing to hear.
Check the states of the button in your NIB file.
It's possible that you are looking at the "active" state or something rather than the more common UICONTROLSTATENORMAL.
Honestly not sure what's wrong with the subclass, but the 'Net (including SO) is full of cautionary tales about subclassing UIButton, and how you shouldn't.
So I'll go with method swizzling on the four touch processing methods. The following function replaces the provided method of a button with my implementation (taken from the MyButton class), while saving the old one in the system button class under a different selector:
//Does NOT look at superclasses
static bool MethodInClass(Class c, SEL sel)
{
unsigned n,i ;
Method *m = class_copyMethodList(c, &n);
for(i=0;i<n;i++)
{
if(sel_isEqual(method_getName(m[i]), sel))
return true;
}
return false;
}
static void MountMethod(Class &buc, SEL SrcSel, SEL SaveSlotSel)
{
IMP OldImp = [buc instanceMethodForSelector:SrcSel];
IMP NewImp = [[MyButton class] instanceMethodForSelector:SrcSel];
if(OldImp && NewImp)
{
//Save the old implementation. Might conflict if the technique is used
//independently on many classes in the same hierarchy
Method SaveMe = class_getInstanceMethod(buc, SaveSlotSel);
if(SaveMe == NULL)
class_addMethod(buc, SaveSlotSel, OldImp, "v#:##");
else
method_setImplementation(SaveMe, OldImp);
//Note: the method's original implemenation might've been in the base class
if(MethodInClass(buc, SrcSel))
{
Method SrcMe = class_getInstanceMethod(buc, SrcSel);
if(SrcMe)
method_setImplementation(SrcMe, NewImp);
}
else //Add an override in the current class
class_addMethod(buc, SrcSel, NewImp, "v#:##");
}
}
And call it so:
Class buc = [bu class];
MountMethod(buc, #selector(touchesBegan:withEvent:), #selector(MyButton_SavedTouchesBegan:withEvent:));
MountMethod(buc, #selector(touchesCancelled:withEvent:), #selector(MyButton_SavedTouchesCancelled:withEvent:));
MountMethod(buc, #selector(touchesEnded:withEvent:), #selector(MyButton_SavedTouchesEnded:withEvent:));
MountMethod(buc, #selector(touchesMoved:withEvent:), #selector(MyButton_SavedTouchesMoved:withEvent:));
This has the disadvantage of mounting the said methods for all buttons, not just for the ones desired. In the MyButton's implementation, there's an additional check if the drag-drop functionality is to be enabled for this particular button. For that I've used associated objects.
One fine point is that touchesXXX methods are implemented in the UIControl class, not in the UIButton. So my first, naive implementation of swizzling would replace the method in UIControl instead of the button class. The current implementation does not assume either way. Also, it makes no assumptions about the run-time class of buttons. Could be UIButton, could be anything (and in real iOS, it's UIRoundedRectButton).
I'm looking for a non-hackish solution for this, so basically -inputView. The part that I'm not sure about is how to make it look like the regular keyboards, from the background to the keys. I realize that I could photoshop an apple keyboard, but this seems like it is a little hackish, especially if apple (probably not but still possible) decides to change the look of their keyboards. I know Numbers has done an excellent job of making extra keyboards that look like the standard system ones, and I would like to do it like those (although obviously they have access to the same resources that made the system keyboards, including possible private frameworks, etc.)
I used the following:
tenDigitKeyboard.m
-(IBAction)pressedKey:(UIButton *)sender
{
[delegate pressedKey:sender.tag];
}
where delegate is defined as `id delegate;
then in the delegate i do...
-(void)pressedKey:(NSInteger)key
{
NSString * bufferString = model.string;
if (key == -1) {//delete
model.string = [bufferString substringWithRange:NSMakeRange(0, [bufferString length]-1)];
}else{
//will need to change the following to lookup key value based on a lookup of the button.tag
model.string = [bufferString stringByAppendingFormat:#"%i",key];
}
[self update];//updates the view
}
I got the keyboard button artwork from: http://www.teehanlax.com/blog/iphone-gui-psd-v4/
Create a view controller and xib. The xib should have 1-9,0 and delete buttons mapped to IBOutlets in your controller. Store and retain the return value string as a property. You can add decimals, etc. if you wish. In the header, store an edition block closure with a property (or alternatively create a delegate or use notification).
#property (copy) void(^valueChangedBlock)(NSString* string);
On touch up, each button sends an event to a method like this:
- (IBAction) pressKey:(id)sender
{
NSString *toAppend;
// Instead of this switch you can store the values in a dictionary mapped by sender.
switch(sender)
{
case oneButton: toAppend=#"1"; break;
case twoButton: toAppend=#"2"; break;
...
}
returnValue = [returnValue appendString:toAppend];
valueChanged(returnValue);
}
Obviously the delete key should remove a character from the end of the string instead of appending. Other than creating the controller and adding this view as the inputView, you should add the valueChangedBlock and set it to update the text field. You may want to put a clear custom button over the text field set to make the field first responder so it doesn't appear as if the user can edit at any point in the string.
-(void) alertView: ( UIAlertView *) alertView
clickedButtonAtIndex: ( NSInteger ) buttonIndex {
// do stuff
// if you want the alert to close, just call [ alertView release ]
}
Can someone explain this method? I'm used to methods that are like "-(IBAction)buttonPress:(id)sender" but this one has three. What do each of those mean?
MethodName : ReturnedType : InputType
is this right?
It's actually:
-(return type) methodName:(param1 type)param1 moreMethodName:(param2 type)param2
Etc, with as many parameters as you want. So that method is called alertView:clickedButtonAtIndex: -- it just has its parameters embedded. It's the equivalent, in a more "normal" language, of alertViewClickedButtonAtIndex(UIAlertView *alertView, NSInteger buttonIndex)
For a pretty good primer on Obj-C syntax, check out:
http://www.cocoadevcentral.com/d/learn_objectivec/
For info on that particular method, check out this document.
Objective-C methods with arguments:
A method with no arguments:
-(void)methodName;
Signature is methodName.
A method with 1 argument:
-(void)methodName:(ArgumentType *)anArgument;
Signature is methodName:.
A method with 2 arguments
-(void)methodName:(ArgumentType1 *)argument1 andArgumentType2:(ArgumentType2 *)argument2;
Signature is methodName:andArgumentType2:
So this is method is a method of 2 arguments: a UIAlertView object and an NSInteger (not an object, simply syntactic sugar for either an int or long depending on your system).
The UIAlertView is the alert view whose delegate has been set to the object of this class. It's usually set when the alert view is created.
The buttonIndex is the index of the button on the UIAlertView that the user touched. This method is called when that button is clicked. By default, nothing is done, and the alert simply vanishes.
You use this method if you want an alert with buttons to pop up, and, when the user clicks on one of the buttons, have the class that invoked the alert do something (possibly different things depending on which button was clicked).
It is a delegate protocol method implementation.
You can find some details about that pattern at iPhone Dev Central.
The class that implements that method acts as a delegate of an UIAlertView.
This way you can customize the behavior of an instance of a class without subclassing.
Its a method with two input arguments.
Similar to:
void someMethod(int i, int j){}
It always a good idea to pick up a book and get the basics right instead of learning in bit and pieces. Trust me :)
When writing my objective-C I prefer to format the method as follows as I think it makes the separation of return type and parameters clearer:
-(void) // return type
alertView:(UIAlertView *) alertView // param1
clickedButtonAtIndex:(NSInteger) buttonIndex // param2
{
// do stuff
// if you want the alert to close, just call [ alertView release ]
}