Working with a button in a custom tableview Cell - objective-c

I have a facebook like button in a custom tableview cell. In the class of my tableview I have the following function.
- (IBAction)sendLike:(id)sender
WithString: (NSString *)shareString
andUrl:(NSURL *)shareUrl{
//Do all kinds of things
[fbController setInitialText:shareString];
[fbController addURL:shareUrl];
[fbController setCompletionHandler:completionHandler];
[self presentViewController:fbController animated:YES completion:nil];
}
}
In my cellForRowAtIndexpath I am trying to call this method in the following way.
NSString *shareString = video.name;
NSURL *shareUrl = [NSURL URLWithString:video.url];
[cell.facebook addTarget:self action:#selector(sendLike: WithString:shareString andUrl:shareUrl)
forControlEvents:UIControlEventTouchUpInside];
But it is complaining that I should put ':' in my #selector. Can anybody help ?
Thanks in advance.

You can try like this
cell.facebook.tag = indexPath.row.
[cell.facebook addTarget:self action:#selector(sendLike:)
forControlEvents:UIControlEventTouchUpInside];
in sendLike event
- (IBAction)sendLike:(id)sender
{
//if you want to fetch data from any list then try
//UIButton *selectedButton = (UIButton*)sender;
//data = [dataList objectAtIndex:selectedButton.tag];
NSString *shareString = video.name;
NSURL *shareUrl = [NSURL URLWithString:video.url];
[fbController setInitialText:shareString];
[fbController addURL:shareUrl];
[fbController setCompletionHandler:completionHandler];
[self presentViewController:fbController animated:YES completion:nil];
}

In your cellForRow:
//wiring a custom string to a button
objc_setAssociatedObject(yourButton, "theKey", strText, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
In your button handler:
NSString *str = objc_getAssociatedObject(sender, "theKey");
p.s don't forget to import runtime library:
#import <objc/runtime.h>

I think you invoked the selector in wrong way. You actually should write,
[cell.facebook addTarget:self action:#selector(sendLike: WithString: andUrl:)
forControlEvents:UIControlEventTouchUpInside];
But this doesn't pass arguments for you. If you want to pass arguments, you should call performSelector like
[self performSelector:#selector(sendLike: WithString: andUrl:)
withObject: cell.facebook
withObject: shareString
withObject: shareUrl];
Hope it will help solving your problem.

As its in the apple documentation, the only available specifications for an #selector in an action are the following methods:
- (void)action;
- (void)action:(id)sender;
- (void)action:(id)sender forEvent:(UIEvent *)event;
As you can see, your selector doesn't match this, because you have multiple arguments, but only the sender id is allowed.
As a solution, you can probably set a tag for your button and then handle it in your selector.

Your problem is that the number and kind of arguments are incorrect in your selector. You can only use targets with the following scheme:
- (void)action;
- (void)action:(id)sender;
- (void)action:(id)sender forEvent:(UIEvent *)event;
You will have to include your information in the sender (the UITableViewCell class instance) object. You can do that as described in the other answer by Stas, but I would recommend you to create a custom table view cell subclass which adds a property for shareString and shareURL. This approach will require more code, but make it much easier to read, maintain and prevents this ugly Obj-C and C mix up.

Related

UIImageView setImage not working in a delegate method?

I have a problem calling the setImage function in the opencv delegate method processImage.
When I call setImage in viewDidLoad, I can see the image, but when I do the same in processImage, it doesn't work.
What's the problem here?
- (void)viewDidLoad
{
[super viewDidLoad];
// This works !
[processImageView setImage:[UIImage imageNamed:#"resistor3.jpg"]];
}
- (void)processImage:(cv::Mat&)img {
// This does not work anymore !
[processImageView setImage:[UIImage imageNamed:#"resistor3.jpg"]];
}
When you modify the UI you must do it from the main thread, chances are that the delegate method, if it's being called, is called on another thread. Try this.
- (void)processImage:(cv::Mat&)img {
dispatch_async(dispatch_get_main_queue(), ^{
[processImageView setImage:[UIImage imageNamed:#"resistor3.jpg"]];
// I also think you should use the dot syntax, but that's purely a style thing
// processImageView.image = [UIImage imageNamed:#"resistor3.jpg"];
});
}
EDIT: Add recommendation about using dot syntax

Unit test an action on UITextField

I have a simple if condition that, when met, adds a target to drop the keyboard when "Done" is tapped
- (void)addDoneWhenTrue:(UITextField *)input
{
if (YES)
{
[input addTarget:self action:#selector(textFieldFinished:) forControlEvents:UIControlEventEditingDidEndOnExit];
}
}
- (IBAction)textFieldFinished:(id)sender
{
[sender resignFirstResponder];
}
I'm using OCMock and OCUnit on iOS5. How can I create a unit test that allows me to pass in a simple UITextField input and simulate tapping the done button on it to verify that resignFirstResponder does happen (or not)?
You shouldn't need to mock it, and this target/action approach feels like a really roundabout way to get to what you want. As long as your controller is the UITextField's delegate, textFieldShouldReturn: will be fired when the Return (or Done in this case) key is tapped.
#pragma mark - UITextFieldDelegate
-(void)textFieldShouldReturn:(UITextField *)textField {
if (someConditionThatDeterminesWhetherKeyboardShouldCollapse) {
[textField resignFirstResponder];
}
}
Then you can test calling textFieldShouldReturn: on your controller when your condition is and isn't met. If you want, you could also make sure your view has been loaded in the test case by calling loadView, and then assert that the text field's delegate is your controller, which will ensure the view is wired up correctly.
-(void)testDoneShouldCollapseKeyboard {
[controller loadView];
expect(controller.textField.delegate).to.beIdenticalTo(controller);
id mockTextField = [OCMockObject partialMockForObject:controller.textField];
[[mockTextField expect] resignFirstResponder];
controller.someConditionThatDeterminesWhetherKeyboardShouldCollapse = YES;
[controller textFieldShouldReturn:controller.textField];
[mockTextField verify];
[[mockTextField reject] resignFirstResponder];
controller.someConditionThatDeterminesWhetherKeyboardShouldCollapse = NO;
[controller textFieldShouldReturn:controller.textField];
[mockTextField verify];
}
Surely you don't need to simulate clicking on a button as that's what UIKit is taking care of. You are concerned that when the textFieldFinished: method is called the right thing happens
Disclaimer
I've not used OCMock for a long time so this is an example just using the docs so it could very easily be slightly incorrect - hopefully someone can correct who has OCMock all setup.
id mock = [OCMockObject mockForClass:[UITextField class]];
[[mock expect] resignFirstResponder];
[sut textFieldFinished:mock]; // sut = system under test - taken from your screen cast ;)
[mock verify];

Parse and create an NSAttributedString when an UITextView's text changes

I'm trying to parse certain parts of the string when a user types into an UITextView or the setText: method is called, and then setting an NSAttributedString back into the text view. However in my current implementation this causes an infinite recursive loop. Since setting the new attributed text causes the text to change (and the notification to fire) whereby I then re-parse the text.
Somebody suggested I use some kind of flag, so while i'm parsing and setting the text, I don't keep doing it. Though this doesn't seem like the optimal solution. Here's a snippet of my code...
CustomTextView.h (UITextView subclass)
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(textViewDidChange:) name:NSTextViewTextDidChangeNotification object:self];
CustomTextView textViewDidChange:
- (void)textViewDidChange:(NSNotification *)notification;
{
__block NSString *string = self.text;
dispatch_async(parserQueue, ^{
NSAttributedString *parsedString = [self parseAttributesForString:string];
dispatch_async(dispatch_get_main_queue(), ^{
[self setAttributedText:parsedString];
});
});
}
CustomTextView setText:
- (void)setText:(NSString *)text
{
[super setText:text];
[self textViewDidChange:nil];
}
Thanks!
Okay so first I'd add an observer to the property text instead of subclassing the class and post a notification. Next, I'd just check what class the text object is. I would this by calling [text isKindOfClass:[NSString class]]. By calling this you know whether the object needs to get parsed again.

Retrieving Custom Button Property in Objective-C

I've created a custom button called TaskUIButton that inherits from UIButton. The only difference I have right now is a "va" property.
Here's the interface
// TaskUIButton.h
#interface TaskUIButton : UIButton
{
NSString *va;
}
#property(nonatomic, retain) NSString *va;
#end
And the implementation file
//TaskUIButton.m
#implementation TaskUIButton
#synthesize va;
#end
Now, I've got an action that I'm using which I want to use to set and retrieve the va property of a button (just for testing/experimentation of course).
Here is where the button action is
- (IBAction)setAndRetrieveVa:(id)sender{
TaskUIButton *imaButton = [TaskUIButton buttonWithType:UIButtonTypeRoundedRect];
imaButton.va = #"please work";
NSLog(#"%#", imaButton.va);
}
Upon activating the setAndRetrieveVa: action, my app crashes with:
-[UIRoundedRectButton setVa:]: unrecognized selector sent to instance 0x4b3a5a0
I'm sure that its a stupid mistake on my part, but I've been going at it for a while and would love some insight!
Thanks!
You are getting this because buttonWithType: is returning a new object which is a UIRoundedRectButton object which is a subclass of UIButton. You can't alter this behavior of the method unless you override but you are unlikely to get what you want. You should take the alloc-init approach.
Using Associative References
You will need to #import <Foundation/NSObjCRuntime.h> for this to work.
To set,
objc_setAssociatedObject(button, "va", #"This is the string", OBJC_ASSOCIATION_RETAIN);
And to retrieve,
NSString * va = (NSString *)objc_getAssociatedObject(button, "va");
This way you wouldn't need to subclass UIButton.
I ended up just extending UIControl...turned out to be alot easier :)
- (IBAction)setAndRetrieveVa:(id)sender{
TaskUIButton *newTaskButton = [[TaskUIButton alloc] initWithFrame:CGRectMake(29.0, (76.0+ (88*taskCounter)), 692, 80.0)];
[newTaskButton addTarget:self action:#selector(function1:)forControlEvents:UIControlEventTouchUpInside];
[newTaskButton addTarget:self action:#selector(function2:) forControlEvents:UIControlEventTouchDragExit];
[newTaskButton setBackgroundColor:[UIColor grayColor]];
[newTaskButton setTitle:#"0" forState:UIControlStateNormal];
[newTaskButton setVa:#"please work!"];
NSLog(#"%#", newTaskButton.va);
}
And for click highlighting, I can always add a function that changes the background color when touch-down occurs, and switches the color back when touch-up occurs. Hurrah!

Declaring UITouchGestures?

I'm working on using a UISwipeGestureRecognizerDirectionRight method to change views within an application and i have used the following code in the main file of the View Controller but it seems as though i need to define the gesture after the asterisk and declare it as this build error states:
"swipeGesture undeclared"
-(void)createGestureRecognizers {
UISwipeGestureRecognizerDirectionRight * swipeGesture = [[UISwipeGestureRecognizerDirectionRight alloc]
initWithTarget:self
action:#selector (handleSwipeGestureRight:)];
[self.theView addGestureRecognizer:swipeGesture];
[swipeGesture release];
}
-(IBAction)handleSwipeGestureRight {
NC2ViewController *second2 =[[NC2ViewController alloc] initWithNibName:#"NC2ViewController" bundle:nil];
second2.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
[self presentModalViewController:second2 animated:YES];
[second2 release];
}
So my question is how do i declare the "swipeGesture" after the asterisk in the header file or have i done something wrong?
Thank You
UISwipeGestureRecognizerDirectionRight is an enum value for the four possible directions. It is not a class you instantiate to recognize gestures. Use UISwipeGestureRecognizer instead:
UISwipeGestureRecognizer *swipeGesture = [[UISwipeGestureRecognizer alloc]
initWithTarget:self
action:#selector (handleSwipeGestureRight:)];
//Set the direction you want to detect by setting
//the recognizer's direction property...
//(the default is Right so don't really need it in this case)
swipeGesture.direction = UISwipeGestureRecognizerDirectionRight;
[self.view addGestureRecognizer:swipeGesture];
[swipeGesture release];
Also, the handler method should be:
-(IBAction)handleSwipeGestureRight:(UISwipeGestureRecognizer *)swipeGesture {
because in the selector for the action, you put a colon in the method name meaning you want it to pass the sender object as the first parameter. (You could also remove the colon from the selector instead if you don't need the sender in the handler.)
Finally, void is more appropriate than IBAction in the handler since it won't be called from an object in a xib. However, since IBAction and void are the same thing, it won't cause a problem.