Longpress On UI Element (Or How To Determine Which Element Is Pressed?) - objective-c

I have an app that has a rather large help section in PDF form. It's getting to me that the PDF is so large and I can tell in instruments that it's putting a little too much strain on the processor, so I'm going to phase it out.
I figured, a lighter implementation would be using a UILongPressGestureRecognizer that I could attach to every UI element that would display specified text in maybe a popover or a UIMenuController indicating the function of the selected element.
So my question is this: How can I attach something like a tag to EVERY element in the view so it can be passed to a single method? When I tried tags, I found there was no way to access it through the (id)sender section of my method signature and thus there was no way to differentiate between the elements.
EDIT: to the person below: While you have solved my facepalm of a question as to determining the tags of views, how might one go about attaching gesture recognizers to a UIBarButtonItem so as to ascertain it's tag? Your implementations allow for a very nasty unrecognized selector because UIGestureRecognizers dont have a tag property.

You can derive the tag from an object passed in as the sender. Just have to check it's class and cast it appropriately. tag is a UIView property so we'll start there.
- (void)someMethod:(id)sender
{
if (![sender isKindOfClass:[UIView class]])
return;
UIView *senderView = (UIView *)sender;
NSInteger tag = senderView.tag;
}

You can access tags through the -(IBAction)xxxxxx:(id)sender; like so:
NSInteger tagValue = [sender tag];
But why can't you just connect the actions to your elements through Interface Builder?
What are the UI elements your using here?

Related

How to specify accessibility identifiers for multiple UIPickerViews during XCUITest

When developing Xcode UI testcases for a view controller with multiple UIPickerViews I ran into several bugs preventing success all relating to being able to uniquely identify the pickers within XCUITest.
What "should" work is to simply set the accessibility identifier, or accessibility label, from within storyboard like so:
But this does not work at all for a UIPickerView, though I verified the accessibilityLabel and accessibilityIdentifier properties are set for the UIPickerView. And yes, I tried it with one or the other or both set. I even tried programmatically setting one or the other or both. The below lines within an XCUITest case fail to locate the picker regardless:
XCUIElement *shippingMethodPicker = app.pickerWheels[#"Shipping method"];
[shippingMethodPicker adjustToPickerWheelValue:#"USPS Media Mail"];
It would seem that this is a known issue, and that the solution would be to make the view controller also a UIPickerViewAccessibilityDelegate, and implement the - (NSString *)pickerView:(UIPickerView *)pickerView accessibilityLabelForComponent:(NSInteger)component delegate method.
The Apple API Documentation would seem to describe exactly what we need to uniquely apply an accessibility label to each pickerWheels component.
But this is also bugged, the pickerView parameter is not actually a UIPickerView *, as referenced in this stackoverflow link Unable to get pickerView.tag in -pickerView:accessibilityLabelForComponent: method
Due to the implementation defect with the delegate method, you cannot determine which UIPickerView the delegate is being called for rendering it useless for a view with more than one picker.
With the storyboard approach bugged, and the accessibility delegate also bugged, I could not locate a way to uniquely identify two or more UIPickerViews in a view controller from within a XCUITest testcase.
Anyone have a solution?
Focusing on the previous stackoverflow link comments, I determined a solution that worked for my requirements.
From the debugger we can confirm the previous links comment observation:
The accessibility delegate method,
- (NSString *)pickerView:(UIPickerView *)pickerView accessibilityLabelForComponent:(NSInteger)component;
is quite bugged and does not have a UIPickerView *, but rather a private class UIAccessibilityPickerComponent *. As we can note in the debugger, there is an actual UIPickerView * at the private _picker property of this private class.
Radar opened.
Well this is an internal test problem, it's not something we would ship in the app for the App Store. So we CAN use private interfaces to get around this problem. We will only compile this when we are performing UI testing.
First, create a new build configuration in Xcode that you would only use for Testing, duplicated from Debug. Within that create a new preprocessor define -DXCUITEST and be sure to set this new build config in your scheme for Test.
Then implement the accessibility delegate as follows:
#pragma mark - UIPickerViewAccessibilityDelegate
#ifdef XCUITEST
- (NSString *)pickerView:(UIPickerView *)pickerView accessibilityLabelForComponent:(NSInteger)component {
NSString *label;
UIPickerView *realPickerView;
Ivar picker;
// we are going to work around a bug where the pickerView on this delegate is the wrong class by
// pulling the UIPickerView * that we need from the private property of the UIAccessibilityPickerComponent class
picker = class_getInstanceVariable([NSClassFromString(#"UIAccessibilityPickerComponent") class], "_picker");
// check if the bug still exists and apply workaround only if necessary
if (![pickerView isKindOfClass:[UIPickerView class]])
realPickerView = object_getIvar(pickerView, picker);
else
realPickerView = pickerView;
if (realPickerView == self.shippingMethod)
label = #"Shipping method";
else if (realPickerView == self.someOtherPicker)
label = #"SomeOtherPicker";
return label;
}
#endif
With this workaround the XCUITest testcases finally executed as expected, successfully testing situations of two and even three UIPickerViews on a single view all uniquely identified. Note in my case these were single wheel pickers, if you wanted to solve the problem for multi-wheel pickers, then implement the component logic in the delegate, which is not bugged and works as expected.
Also, don't forget to add this header to the top of your view controller class file:
#ifdef XCUITEST
#import <objc/runtime.h>
#endif

Where to define NSArray and where to define Button action?

I have made an array of text strings and want to pull these out an into a label by EITHER swiping of pressing a button. So i have two different functions/methods, the button and the swipe method.
Where and how do I define the array so that these methods can refer to it? Should it be a 'extern NSArray' ?
I have uploaded the image of full code externally http://s1.postimg.org/b2e3m4v67/Sk_rmbillede_2014_05_11_kl_15_48_28.png
Not sure though if that's a violation of some rules here(?)
You want the quote to change on swipe/button press.
In your button press/swipe methods you're setting the text property of the VC's label property to something called Quoteselected. And it looks like Quoteselected is a random element of the array Quotes - or at least maybe it is, since that random number could be 6-10, and you don't have any objects in the Quotes array at those indices - so if those numbers are ever generated by the random function, your program will crash due to an index out of bounds error.
What you probably want to do is generate a new random number on each user interaction and then at that point change the value of Quoteselected to be the object at that index of the array. And then assign that to the label's text property.
As far as defining the Array - I wouldn't have done it the way you did. What you've got there is an "ivar", an instance variable. On iOS, those are typically properties. And since it's a "private" array that outside classes won't need to know about, I'd declare it as a part of the class extension.
So,
#interface BOViewController()
#property NSArray *quotes;
#end
Also note my capitalization changes, that's better style.
So now you've got an array property declared, but there's no data in it. It depends on how you created your View Controller instance. Assuming you did it in a storyboard, it would go in awakeFromNib: or viewDidLoad: (if you instantiated the VC automatically, you might put it in the initWithNibName: method).
- (void)viewDidLoad {
[super viewDidLoad];
self.quotes = #[#"Test", #"Number 3"...];
Then when you want to reference the array in other parts of the class:
self.label.text = self.quotes[0];
Note that your existing code should work, it's just not typical Cocoa coding style.

Cocoa - capturing specific events

I'm fairly new to Cocoa programming, and have a question about control event handling.
I create an 'action' for a button, and get an updated AppDelegate.m to handle this eg.
- (IBAction)seedBtnPressed:(id)sender {
NSString* myString = #"Hi there";
[_updateLbl setStringValue:myString];
}
When running this, pressing the 'seed' button does what it should - the label updates.
My question is: why have I captured the 'button press event' by default, as I don't see any place where I've specified this. Alternately, how would I capture a mouse-over event with an action? I gather I'd create another action for the button, but am not sure how to specify this to handle 'mouse-over' events only? Sorry if I've used Windows terminology here, I understand Cocoa uses different names for things.
Thanks
Pete
You need to Subclass the NSButton class (or even better the NSButtonCell class).
- (void)mouseEntered:(NSEvent *)theEvent;
- (void)mouseExited:(NSEvent *)theEvent;
They should get called when the mouse enter and exit the area. You may also need to re create the tracking area, look here:
- (void)updateTrackingAreas
For fade in and fade out effect I played with animator and alpha value for example:
[[self animator]setAlphaValue:0.5];
To get mouse-over events for an NSView you should use the NSTrackingArea class (assuming you're targeting a relatively modern version of OS X). Apple have good documentation on this available at http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/EventOverview/TrackingAreaObjects/TrackingAreaObjects.html
For your other query about the seedBtnPressed: triggering although you don't specify it - have you set an action in Interface Builder for the button rather than programmatically?

Trouble accessing NSTextField from method called by a Custom View

I'm almost done writing an image editing program, but ran into a problem that should have a simple solution.
Basically, I've built a set of buttons and NSTextFields and a Custom View into the main xib, just dropping them straight off the Library into the default window (and then, of course, linking them up with IBOutlets and IBActions). One of the buttons is an "Open" button that calls a function. The function does several things: it runs a NSOpenPanel, and then changes some of the NSTextFields (used for changing image name and path). That code is called as follows:
- (IBAction)openButtonPressed: (id)sender {
[self runOpenPanel];
}
Now I also happen to be running my keyDown handler from the Custom View, as I've told it to acceptFirstResponder. (I know, it's probably bad practice to not write a separate Controller class, but that's best left for another time.) So my keyDown event looks like this (simplified, as in the actual code I have has if statements to handle certain keys separately that don't pertain to this question):
- (void)keyDown: (NSEvent *)theEvent {
[self runOpenPanel];
}
So they both use [self runOpenPanel] but from different contexts. The problem I'm having is that "runOpenPanel" makes a few calls to change IBOutlets, like this:
-(void)runOpenPanel {
// Omitting some of the trivial NSOpenPanel code
[myTextField setStringValue: #"The file name NSString from the aforementioned omitted code."];
}
So myTextField updates when the function is run by an Interface Builder button, but not when run by the Custom View's keyDown handler. Is there some way to call runOpenPanel that will allow it to access myTextField? I've tried using [[super self] runOpenPanel] (don't laugh) and a number of other things. Thanks in advance!

Descendant as an parent's delegate

Lets say I want to create my own text view with maximum characters constrain. And I want to do that constrain in level below - in text view.
I think of creating CustomTextView : UITextView where customTextView.delegate would be the same object - customTextView (self.delegate = self). The definition of the class would be CustomTextView : UITextView <UITextVIewDelegate> and I would implement – textView:shouldChangeTextInRange:replacementText: to do the constrain logic in.
But somehow this do not work. Can I get explanation why or what can be wrong and how to achieve my intent?
If you are subclassing UITextView, why would you need to set itself as the delegate? The delegate is only used to notify code outside of the UITextView that something changed in the UITextView. This means that the UITextView is notified of changes to itself first and, using the delegate, you can notify external code (UIViewController, etc.) of what happened. If you are subclassing the UITextView, it should receive those change notifications from the OS.
However, looking through the documentation, I cannot see how you would track the built-in events by subclass alone. Here's an article I found with a Google search: Subclassing a UITextView