I have the following code that updates the value of a textfield with the current time. My question is: Why do you send nil as the sender at the bottom part of the code? [self showCurrentTime:nil];
CurrentTimeViewController.m
- (IBAction)showCurrentTime:(id)sender
{
NSDate *now = [NSDate date];
static NSDateFormatter *formatter = nil;
if(!formatter) {
formatter = [[NSDateFormatter alloc] init];
[formatter setTimeStyle:NSDateFormatterShortStyle];
}
[timeLabel setText:[formatter stringFromDate:now]];
}
...
- (void)viewWillAppear:(BOOL)animated
{
NSLog(#"CurrentTimeViewController will appear");
[super viewWillAppear:animated];
[self showCurrentTime:nil];
}
Because normally when action handlers are called, they pass the object that initiated the call to the method, like a UIButton, or UISegmentedControl. But, when you want to call the method from your code, and not as the result of an action, you cannot pass a human as sender, so you pass nil.
Also, the - (IBAction) indicates that this method can be connected to an event via the Interface Builder by dragging a Touch Up Inside (or touch down outside/etc) event from a button (or any other control that has some sort of event) to the File's Owner and selecting thumbTapped:.
For example:
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
button.tag = 1001;
[button addTarget:self action:#selector(thumbTapped:) forControlEvents:UIControlEventTouchUpInside];
When the touch is released (and the touch is still inside the button), will call thumbTapped:, passing the button object as the sender
- (IBAction)thumbTapped:(id)sender {
if ([sender isKindOfClass:[UIButton class]] && ((UIButton *)sender).tag == 1001) {
iPhoneImagePreviewViewController *previewVC = [[iPhoneImagePreviewViewController alloc] initWithNibName:#"iPhoneImagePreviewViewController" bundle:nil];
[self.navigationController pushViewController:previewVC animated:YES];
[previewVC release];
} else {
[[[[UIAlertView alloc] initWithTitle:[[[NSBundle mainBundle] infoDictionary] objectForKey:#"CFBundleDisplayName"]
message:#"This method was called from somewhere in user code, not as the result of an action!"
delegate:self
cancelButtonTitle:#"OK"
otherButtonTitles:nil] autorelease] show];
}
}
IBAction methods, in their most common form, take a single sender argument. When invoked by the system, they pass the control that triggered the action as the sender parameter. If you're going to call the method directly, you'll need to provide a sender. Since this method isn't being invoked by a control that's just been interacted with by the user, nil is used.
I actually think that calling actions directly isn't a good pattern to follow. Having a method with the tag IBAction implies "This method is invoked in response to user action" and that's an important bit of context to be able to depend on when working within that method. If code is going to call the method directly that idea's been violated.
Usually I think it's better to do something like this:
- (void)updateTime:(NSDate *)date {
static NSDateFormatter *formatter = nil;
if(!formatter) {
formatter = [[NSDateFormatter alloc] init];
[formatter setTimeStyle:NSDateFormatterShortStyle];
}
[timeLabel setText:[formatter stringFromDate:date]];
}
- (IBAction)showCurrentTime:(id)sender {
[self updateTime:[NSDate date]];
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self updateTime:[NSDate date]];
}
It's because you need to send something, since the signature of the method you're calling takes an argument.
The :(id)sender is commonly seen in button press actions. When you connect a button in a nib to a method in your view controller, it will check to see if it can take an argument. If it does, the "sender" will point to the instance of the UIButton that was created in the nib.
This is also true for programmatic button creation, and many other cases where you send in selectors.
The specifcation for the showCurrentTime function has 1 argument named sender. If you called the function without sending the ID of an object the call would be invalid.
Nil is used instead of NULL in objective-c and it is purely being sent to satisfy the specification of the function you are calling.
As the function does not actually use the argument within the body it does not actually matter what object you send to the function.
Related
I know the ARC in iOS 5 but I'm now developing pre-iOS 5 code style, and want to solve this problem by a manual release approach.
My only goal for this is to make a very handy custom alert view with UITextField.
I have a 'BigView' view that has many functions in it. And it can possibly generate many UIAlertView for many different situation on the display with that view. So I know the way use UIAlertViewDelegate for each alert view, but kind of experimentally try want to make this as like UIButton's 'addTarget'(actually it's UIControl's method).
Briefly,
This is in the part of 'BigView' class and my 'TextAlert' instance fired by a button for email gathering .
BigView.m
- (void)emailFeedback:(id)sender
{
TextAlert *textAlert = [[TextAlert alloc] initWithTitle:#"Enter your email address"];
[textAlert setTarget:self action:#selector(textAlertInputed:)];
// [textAlert release];
}
- (void)textAlertInputed:(NSString *)text
{
NSLog(#"text alert inputed, text: %#", text);
}
and these are full my TextAlert files.
TextAlert.h
#import <Foundation/Foundation.h>
#interface TextAlert : NSObject <UIAlertViewDelegate>
{
UIAlertView *alertView;
UITextField *textField;
id target;
SEL action;
}
- (id)initWithTitle:(NSString *)title;
- (void)setTarget:(id)target action:(SEL)action;
#end
TextAlert.m
#import "TextAlert.h"
#implementation TextAlert
- (id)initWithTitle:(NSString *)title
{
if (self = [super init])
{
alertView = [[UIAlertView alloc] initWithTitle:title message:#"beneath" delegate:self cancelButtonTitle:#"Cancel" otherButtonTitles:#"OK", nil];
textField = [[UITextField alloc] initWithFrame:CGRectMake(12, 45, 260, 25)];
CGAffineTransform myTransform = CGAffineTransformMakeTranslation(0, 60);
[alertView setTransform:myTransform];
[textField setBackgroundColor:[UIColor whiteColor]];
[alertView addSubview:textField];
[alertView show];
}
return self;
}
- (void)dealloc
{
[alertView release]; alertView = nil;
[textField release]; textField = nil;
[super dealloc];
}
- (void)setTarget:(id)_target action:(SEL)_action
{
target = _target;
action = _action;
}
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
[target performSelector:action withObject:textField.text];
}
#end
So my main problem is the releasing point of TextAlert instance in the 'BigView' as you can see the only comment part full codes above. Of course if I remove that comment out, I got crash for call for method of deallocated.
And I also get error make textAlert instance as autoreleased one.
For me, the only solution for this is to make the 'textAlert' object in the 'BigView' a member of 'BigView' not local object. But in that case, my initial goal for handy and lightweight approach for this is not satisfied, I think. And the 'BigView' has already many member instances so I don't want to add any more.
So any suggestions? Or It will be welcome any comment for this trying. I'm ready to hear any
reproves to my insufficient code, really.
Thanks in advance,
MK
If everything works except your release problem you should only consider implementing public "show" method and private "dismiss" method (in your custom alert view).. In show method you should call [self retain] beside other things and on dismiss (add this target to button or whatever dismisses your view) call [self relese].
This isn't directly what you asked for, but could help you anyway.
Handling multiple UIAlertViews in a single UIViewController can be painful. When I ran into this problem, I found an alternative control on github, called BlockAlertsAndActionSheets. It uses blocks instead of delegates, the appearance can be fully customized (even to the default Apple-style) and there is also an "AlertView with an UITextField". Works good for me and I didn't have to reinvent that wheel! ;-)
In a ViewController I have the following:
- (void)viewWillAppear:(BOOL)animated
{
DataObject *theDataObject = [self theAppDataObject];
NSDateFormatter *formatter = [[[NSDateFormatter alloc] init] autorelease];
[formatter setDateFormat:#"MMM dd, yyyy HH:mm"];
NSString *dateStr = [formatter stringFromDate:theDataObject.deadline];
NSLog(#"Logged dateStr: %#", dateStr);
[dateTimeLabel setText:dateStr];
[super viewWillAppear:animated];
}
To clarify: dateTimeLabel IS wired up in the xib file. The viewWillAppear method is explicitly called from another ViewController, and is firing, like so:
- (IBAction)setDateTimeButtonClicked:(id)sender
{
DataObject *theDataObject = [self theAppDataObject];
theDataObject.deadline = [datePicker date];
FirstMobileViewController *mobileVC = [[FirstMobileViewController alloc] init];
[mobileVC viewWillAppear:YES];
[mobileVC release];
[UIView transitionWithView:self.view.superview
duration:0.5
options:UIViewAnimationOptionTransitionFlipFromRight | UIViewAnimationOptionLayoutSubviews | UIViewAnimationOptionAllowAnimatedContent
animations:^{[self.view removeFromSuperview];}
completion:NULL];
}
The viewWillAppear method is firing -- the dateStr is logged by NSLog appropriately when the superview is shown again. But the dateTimeLabel never updates. Obviously, commenting the NSLog line doesn't make a difference.
The MADDENING thing is that even though NSLog logs dateStr just fine, if I change dateStr to, say, #"Yo!" or even to a locally initialized string, then dateTimeLabel will update, no problem.
What am I missing here?
You method to add a child view controller isn't correct. Try with the following code (with your method, when you call ViewWillAppear, think that the view of the view controller isn't yet initialized. (you san check that with a simple hack: adding mobileVC.view; just after the mobileVC initialization)
- (IBAction)setDateTimeButtonClicked:(id)sender {
DataObject *theDataObject = [self theAppDataObject];
theDataObject.deadline = [datePicker date];
FirstMobileViewController *mobileVC = [[FirstMobileViewController alloc] init];
[self addChildViewController:mobileVC];
UIView *superView = self.view.superview;
mobileVC.view.frame = superView.bounds;
[UIView transitionWithView:superview
duration:0.5
options:UIViewAnimationOptionTransitionFlipFromRight | UIViewAnimationOptionLayoutSubviews | UIViewAnimationOptionAllowAnimatedContent
animations:^{[self.view removeFromSuperview];
[superview addSubView:mobileVC.view]}
completion:^(BOOL finished) {
[mobileVC didMoveToParentViewController:self];
}];
}
With this method, the viewWillAppear should be called automatically.
Firstly, you're releasing mobileVC before doing anything with it, so while it runs the code in the viewWillAppear: method, that's all that happens.
You're also not loading the XIB file for mobileVC, since you only alloc and init it. Therefore the objects you reference within viewWillAppear most likely don't exist at the point that you explicitly call the method.
You haven't shown the code that you use to present the mobileVC so it's hard to guess what the fix will be, or to understand the quirky problem you're having with string literals working. But, suffice to say, when you present the new view controller, it will be a different instance than the one you explicitly called.
Possible fix:
Use initWithNibName:bundle: instead of plain init to ensure that your XIB is loaded. Then pass mobileVC into the completion block of your transition to present it?
Put [super viewWillAppear:animated] at the beginning of viewWillAppear.
Maybe you never connect dateTimeLabel in xib to dateTimeLabel.
Try to use [self. dateTimeLabel setText: dateStr]
I have mulitple UITextFields in a view.
I'm assuming the place to capture the vlue of a UITextField once the user comes out of it is to implement the delegate method "textFieldShouldReturn".
Question - In "textFieldShouldReturn" however, how do I tell which of the UITextField's triggered this?
For example assuming at this stage I now need to update my data model with the value of what the UITextField now shows, so need to update the correct field in the model with aligns with that particular UITextField.
PS If there's a better approach, or a way to kind of "binding" approach I'm missing I'd be interested
...or you can skip all the tags and make your UITextViews instance vars and do:
- (void)viewDidLoad {
myTextView1 = [[UITextView alloc] init];
myTextView2 = [[UITextView alloc] init];
myTextView3 = [[UITextView alloc] init];
myTextView4 = [[UITextView alloc] init];
......
}
- (void)textFieldShouldReturn:(UITextField *)textField {
BOOL shouldReturn = NO;
if (textField == myTextView1)
{
shouldReturn = YES;
}
...and so on...
}
... release the instance vars in the dealloc...
I kinda prefer this way, but the other answer will work too.
I'm working on an app that will display a UIAlertView upon hitting it's exit button, only if progress in the game has been made. I was wondering how you would use OCUnit to intercept the UIAlertView and interact with it, or even detect if it has been presented. The only thing I can think of is to monkeypatch [UIAlertViewDelegate willPresentAlertView], but that makes me want to cry.
Does anyone know of a better method of doing this?
Update: See my blog post How to Unit Test Your Alerts and Action Sheets
The problem with my other answer is that the -showAlertWithMessage: method itself is never exercised by unit tests. "Use manual testing to verify it once" isn't too bad for easy scenarios, but error handling often involves unusual situations that are difficult to reproduce. …Besides, I got that nagging feeling that I had stopped short, and that there might be a more thorough way. There is.
In the class under test, don't instantiate UIAlertView directly. Instead, define a method
+ (Class)alertViewClass
{
return [UIAlertView class];
}
that can be replaced using "subclass and override." (Alternatively, use dependency injection and pass this class in as an initializer argument.)
Invoke this to determine the class to instantiate to show an alert:
Class alertViewClass = [[self class] alertViewClass];
id alert = [[alertViewClass alloc] initWithTitle:...etc...
Now define a mock alert view class. Its job is to remember its initializer arguments, and post a notification, passing itself as the object:
- (void)show
{
[[NSNotificationCenter defaultCenter] postNotificationName:MockAlertViewShowNotification
object:self
userInfo:nil];
}
Your testing subclass (TestingFoo) redefines +alertViewClass to substitute the mock:
+ (Class)alertViewClass
{
return [MockAlertView class];
}
Make your test class register for the notification. The invoked method can now verify the arguments passed to the alert initializer and the number of times -show was messaged.
Additional tip: In addition to the mock alert, I defined an alert verifier class that:
Registers for the notification
Lets me set expected values
Upon notification, verifies the state against the expected values
So all my alert tests do now is create the verifier, set the expectations, and exercise the call.
The latest version of OCMock (2.2.1 the at time of this writing) has features that make this easy. Here's some sample test code that stubs UIAlertView's "alloc" class method to return a mock object instead of a real UIAlertView.
id mockAlertView = [OCMockObject mockForClass:[UIAlertView class]];
[[[mockAlertView stub] andReturn:mockAlertView] alloc];
(void)[[[mockAlertView expect] andReturn:mockAlertView]
initWithTitle:OCMOCK_ANY
message:OCMOCK_ANY
delegate:OCMOCK_ANY
cancelButtonTitle:OCMOCK_ANY
otherButtonTitles:OCMOCK_ANY, nil];
[[mockAlertView expect] show];
[myViewController doSomething];
[mockAlertView verify];
Note: Please see my other answer. I recommend it over this one.
In the actual class, define a short method to show an alert, something like:
- (void)showAlertWithMessage:(NSString message *)message
{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:nil
message:message
delegate:self
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[alert show];
[alert release];
}
For your test, don't test this actual method. Instead, use "subclass and override" to define a spy that simply records its calls and arguments. Let's say the original class is named "Foo". Here's a subclass for testing purposes:
#interface TestingFoo : Foo
#property(nonatomic, assign) NSUInteger countShowAlert;
#property(nonatomic, retain) NSString *lastShowAlertMessage;
#end
#implementation TestingFoo
#synthesize countShowAlert;
#synthesize lastShowAlertMessage;
- (void)dealloc
{
[lastShowAlertMessage release];
[super dealloc];
}
- (void)showAlertWithMessage:(NSString message *)message
{
++countShowAlert;
[self setLastShowAlertMessage:message];
}
#end
Now as long as
your code calls -showAlertWithMessage: instead of showing an alert directly, and
your test code instantiates TestingFoo instead of Foo,
you can check the number of calls to show an alert, and the last message.
Since this doesn't exercise the actual code that shows an alert, use manual testing to verify it once.
You can get unit tests for alert views fairly seamlessly by exchanging the 'show' implementation of UIAlertView. For example, this interface gives you some amount of testing abilities:
#interface UIAlertView (Testing)
+ (void)skipNext;
+ (BOOL)didSkip;
#end
with this implementation
#import <objc/runtime.h>
#implementation UIAlertView (Testing)
static BOOL skip = NO;
+ (id)alloc
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Method showMethod = class_getInstanceMethod(self, #selector(show));
Method show_Method = class_getInstanceMethod(self, #selector(show_));
method_exchangeImplementations(showMethod, show_Method);
});
return [super alloc];
}
+ (void)skipNext
{
skip = YES;
}
+ (BOOL)didSkip
{
return !skip;
}
- (void)show_
{
NSLog(#"UIAlertView :: would appear here (%#) [ title = %#; message = %# ]", skip ? #"predicted" : #"unexpected", [self title], [self message]);
if (skip) {
skip = NO;
return;
}
}
#end
You can write unit tests e.g. like this:
[UIAlertView skipNext];
// do something that you expect will give an alert
STAssertTrue([UIAlertView didSkip], #"Alert view did not appear as expected");
If you want to automate tapping a specific button in the alert view, you will need some more magic. The interface gets two new class methods:
#interface UIAlertView (Testing)
+ (void)skipNext;
+ (BOOL)didSkip;
+ (void)tapNext:(NSString *)buttonTitle;
+ (BOOL)didTap;
#end
which go like this
static NSString *next = nil;
+ (void)tapNext:(NSString *)buttonTitle
{
[next release];
next = [buttonTitle retain];
}
+ (BOOL)didTap
{
BOOL result = !next;
[next release];
next = nil;
return result;
}
and the show method becomes
- (void)show_
{
if (next) {
NSLog(#"UIAlertView :: simulating alert for tapping %#", next);
for (NSInteger i = 0; i < [self numberOfButtons]; i++)
if ([next isEqualToString:[self buttonTitleAtIndex:i]]) {
[next release];
next = nil;
[self alertView:self clickedButtonAtIndex:i];
return;
}
return;
}
NSLog(#"UIAlertView :: would appear here (%#) [ title = %#; message = %# ]", skip ? #"predicted" : #"unexpected", [self title], [self message]);
if (skip) {
skip = NO;
return;
}
}
This can be tested similarly, but instead of skipNext you'd say which button to tap. E.g.
[UIAlertView tapNext:#"Download"];
// do stuff that triggers an alert view with a "Download" button among others
STAssertTrue([UIAlertView didTap], #"Download was never tappable or never tapped");
I want to update UILabel by clicking a reload button. Additionally, I want to update the label in background, because it is fetching the new data via XML from my website. Of course it would be nice to auto-update the label when the application is opened. And there is my problem:
I was able to make it work well when user were clicking the button manually. But I don't understand how to do the same by calling my method via "applicationDidBecomeActive". I tried to do it the same way, but it obviously doesn't work because my label is returned nil.
I suppose there is a problem of my understanding and the solution should be quite easy. Thanks for your input! Note: I am a beginner with Objective-C and have sometimes problems with "easy" things. ;-)
Below is a summary of the important code parts:
AppDelegate
- (void)applicationDidBecomeActive:(UIApplication *)application {
[[MyViewController alloc] reloadButtonAction];
}
MyViewController
#synthesize label
- (void)reloadButtonAction {
[self performSelectorInBackground:#selector(updateData) withObject:nil];
}
- (void)updateData {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// Parse the XML File and save the data via NSUserDefaults
[[XMLParser alloc] parseXMLFileAtURL];
// Update the labels
[self performSelectorOnMainThread:#selector(updateLabels) withObject:nil waitUntilDone:NO];
[pool release];
}
- (void)updateLabels {
NSUserDefaults *variable = [NSUserDefaults standardUserDefaults];
myLabel.text = [variable stringForKey:#"myLabelText"];
// myLabel is nil when calling all of this via AppDelegate
// so no changes to the myLabel are done in that case
// but: it works perfectly when called via button selector (see below)
NSLog(#"%#",myLabel.text);
}
-(void)viewDidLoad {
// Reload button in the center
UIButton *reloadButton = [UIButton buttonWithType:UIBarButtonSystemItemRefresh];
reloadButton.frame = CGRectMake(145,75,30,30);
[reloadButton setTitle:#"" forState:UIControlStateNormal];
[reloadButton addTarget:self action:#selector(reloadButtonAction) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:reloadButton];
}
First:
[[MyViewController alloc] reloadButtonAction];
Doesn't make sense. You allocate memory, without initializing an object. And then you want to call a method on it. Doesn't work
Use an instance for it:
[myViewControllerInstance reloadButtonAction];
In your app delegate you should have an reference to your rootcontroller instance if that is the object contains the reload method, use that instance.
Note:
Alloc only reserves space in the memory for an object which size the size of MyViewController instance. An init method will fill it.