UIDatePicker Automation using KIF dateChangedAction not called - ios

I'm automating date selection on UIDatePicker using KIF. I added the accessibility label and set the target for the picker if the date changes.
Ref: http://bit.ly/140ICwo
+(id) changeDate: (NSDate *) myDate
{
[s addStep:[KIFTestStep stepToEnterDate:myDate ToDatePickerWithAccessibilityLabel:#"datePicker"]];
[self wait:s timeInSeconds:3];
[s addStep:[KIFTestStep stepToTapViewWithAccessibilityLabel:#"Done" traits:UIAccessibilityTraitButton]];
return s;
}
- (void) ViewDidLoad
{
...
datePicker.maximumDate = lastAvailableDate;
datePicker.date = (dateValue ? dateValue : [NSDate date]);
[datePicker addTarget:self action:#selector(dateChangedAction:)
forControlEvents:UIControlEventValueChanged];
self.datePicker.accessibilityLabel = #"datePicker";
self.footerLabel.accessibilityLabel = #"datelabel";
}
- (IBAction)dateChangedAction:(id)sender
{
[dateValue release];
dateValue = [datePicker.date retain];
dateCell.detailTextLabel.text = [[[self class] sharedFormatter] stringFromDate:dateValue];
[self setDateTitleText:[[[self class] sharedFormatter] stringFromDate:dateValue]];
}
The Picker rotates and stops at the given date however the "dateChangedAction" function is not getting called, hence the label which displays the selected date is not getting updated.
If I run the app with out KIF everything works fine. Also I tried to manually select a date when running KIF to check it it updates the label but it seems like the UI gets frozen and I cannot click any UI controls.
Looks like the problem is related to this posting
http://bit.ly/10xtbqU
Any help is very much appreciated.
Thanks

I run into the same problem you're just missing
[picker sendActionsForControlEvents:UIControlEventValueChanged];
to trigger the dateChangedAction callback,
in other words try this:
+ (id)stepToEnterDate:(NSDate*)date ToDatePickerWithAccessibilityLabel:(NSString*)label
{
NSString *description=[NSString stringWithFormat:#"Enter date to Date picker with accessibility label '%#'",[date description]];
return [self stepWithDescription:description executionBlock:^(KIFTestStep *step, NSError **error)
{
UIAccessibilityElement *element = [[UIApplication sharedApplication] accessibilityElementWithLabel:label];
KIFTestCondition(element, error, #"View with label %# not found", label);
if(!element)
{
return KIFTestStepResultWait;
}
UIDatePicker *picker = (UIDatePicker*)[UIAccessibilityElement viewContainingAccessibilityElement:element];
KIFTestCondition([picker isKindOfClass:[UIDatePicker class]], error, #"Specified view is not a picker");
[picker setDate:date animated:YES];
// trigger the UIControlEventValueChanged in case of event listener
[picker sendActionsForControlEvents:UIControlEventValueChanged];
return KIFTestStepResultSuccess;
}];
}

Related

Delaying IBAction Button

I want to delay this button for 23 hours, which is 82800 seconds. It should work fine, the button does delay after one click, however when I switch to another view controller, or re-enter the application, the button delay function fails to work as it just pops back to clickable button after switching to another view controller or re-starting the application.
here is the code:
- (IBAction)save:(id)sender
{
UIButton *theButton = (UIButton *) sender;
theButton.enabled = NO;
[self performSelector:#selector(enableButton:) withObject:theButton afterDelay:82800.0];
}
- (void)enableButton:(UIButton *)button
{
button.enabled = YES;
}
I am looking for the code that allows this button to be delayed for 23 hours, no matter if I quit the application or switch to another view controller.
please help
You should use NSUserDefaults. When save: method is called check current date [NSDate date] and save it into the user defaults. Then (when time has already passed) you retrieve the saved date from the defaults and compare it to the current date. If 23 hours have already passed you enable the button
UPDATED:
this how you save the date:
- (IBAction)save:(id)sender
{
UIButton *theButton = (UIButton *) sender;
theButton.enabled = NO;
[[NSUserDefaults standardUserDefaults] setObject:[NSDate date] forKey:#"savedDate"];
[[NSUserDefaults standardUserDefaults] synchronize];
}
then (in future) you fetch the saved date:
NSDate* savedDate = [[NSUserDefaults standardUserDefaults] objectForKey:#"savedDate"];
if ([[NSDate date] timeIntervalSinceDate:savedDate] >= 82800.0 )
{
theButton.enabled = YES;// you need to keep the reference to the button
}

Custom cell UITextField updated in all cells across all sections

This is a dynamic prototype UITableView: In my cellForRowAtIndexPath:
I have dequequed my custom cell and this works just fine:
TimeSetViewCell *cell = [_tableView dequeueReusableCellWithIdentifier:#"timeCell" forIndexPath:indexPath];
Next I set TimePicker to UITextField in timeCell and this works fine as well (i.e. the time picker displayed onclick of UITextField in timeCell) :
// Start Time
UIDatePicker *sTimePicker = [[UIDatePicker alloc]init];
sTimePicker.datePickerMode = UIDatePickerModeTime;
sTimePicker.backgroundColor = [UIColor whiteColor]
[cell.startTripTime setInputView:sTimePicker];
// End Time
UIDatePicker *eTimePicker = [[UIDatePicker alloc]init];
eTimePicker.datePickerMode = UIDatePickerModeTime;
eTimePicker.backgroundColor = [UIColor whiteColor];
[cell.endTripTime setInputView:eTimePicker];
Next I wanted to update the cell's UITextField through an event, in which I have understood from #Mani's post, I have set:
sTimePicker.tag = 1;
eTimePicker.tag = 2;
[sTimePicker addTarget:self action:#selector(updateTextField:)
forControlEvents:UIControlEventValueChanged];
[eTimePicker addTarget:self action:#selector(updateTextField:)
forControlEvents:UIControlEventValueChanged];
And in my custom method -(void)updateTextField:(UIDatePicker *)sender I have included this:
//Date Formatter
NSDateFormatter *dateFormat = [[NSDateFormatter alloc]init];
[dateFormat setTimeStyle: NSDateFormatterShortStyle];
if(sender.tag == 1){
//Get Start Date
UIDatePicker *starttimepicker = (UIDatePicker*)sender.inputView;
thisStartTime = [starttimepicker date];
//Display Date
NSString *startTimeText = [dateFormat stringFromDate:thisStartTime];
sTimeText = [NSString stringWithFormat:#"%#",startTimeText];
}
if(sender.tag == 2){
//Get End Date
UIDatePicker *endtimepicker = (UIDatePicker*)sender.inputView;
thisEndTime = [endtimepicker date];
//Display Date
NSString *endTimeText = [dateFormat stringFromDate:thisEndTime];
eTimeText = [NSString stringWithFormat:#"%#",endTimeText];
}
Question Update
I managed to pass startTimeText and endTimeText to cell.startTripTime.text and cell.endTripTime.text through sTimeText and eTimeText.
And I used [_tableView reloadData]; in -(void)updateTextField:(UIDatePicker *)sender (after the two if blocks) and the data is displayed in my cell.startTripTimeand cell.endTripTime UITextField, but it appears the same in across of my sections, while I want it to change only the first section's time, how can I fix that?
//I couldn't include the image but the output of the UITableView looks like this:
_________________________
Section 1
_________________________
Start Time : 4:50 PM
_________________________
End Time: 5:50 PM
_________________________
Section 2
_________________________
Start Time : 4:50 PM
_________________________
End Time: 5:50 PM
_________________________
And the [_tableView reloadData]; seems to interfere and reloads the view while I haven't even finish picking the time that I wanted, how can I fix this too?
The action method should be sent to one of the picker, the startStripTime seems to be a UIView instance not a UIDatePicker instance.
It is he date picker that changes its value, not the view containing it.
You should change also the update method accordingly.

UIPageViewController's DataSource delegates getting called 2 times continuously for a single swipe

This may look like a previously asked question(PageViewController delegate functions called twice) but the thing is I could not apply that solution to my problem.
As you might notice that I m developing a calendar application and Using UIPageViewController to manage my yearly calendar display. As you can see I'm using UIPageViewControllerTransitionStylePageCurl and when user curls the page(either forward/backward) their respective delegate is getting called twice which is giving me either 2 year increment or decrement based on the delegate that got executed. Now I need to find out what is causing this issue and stop it from getting executed twice.
I know it is important to return a viewController in those delegates which gives my next page or previous page thus I m just refreshing the viewController's view so that I can render the view with new data. I also tried another delegate called willTransitionToViewControllers but wont get me anywhere because willTransitionToViewControllers will get executed only after
viewControllerAfterViewController and viewControllerBeforeViewController.
Someone help me understand and solve this issue.
- (void)addCalendarViews
{
CGRect frame = CGRectMake(0., 0., self.view.frame.size.width, self.view.frame.size.height);
pageViewController = [[UIPageViewController alloc] initWithTransitionStyle:UIPageViewControllerTransitionStylePageCurl
navigationOrientation:UIPageViewControllerNavigationOrientationHorizontal
options:[NSDictionary dictionaryWithObject:[NSNumber numberWithInt:UIPageViewControllerSpineLocationNone] forKey:UIPageViewControllerOptionSpineLocationKey]];
pageViewController.doubleSided = YES;
pageViewController.delegate = self;
pageViewController.dataSource = self;
YearCalendarViewController *yearController = [[YearCalendarViewController alloc] init];
NSArray *viewControllers = [NSArray arrayWithObject:yearController];
[self.pageViewController setViewControllers:viewControllers
direction:UIPageViewControllerNavigationDirectionReverse
animated:YES
completion:nil];
[self addChildViewController:pageViewController];
[self.view addSubview:pageViewController.view];
[pageViewController didMoveToParentViewController:self];
CGRect pageViewRect = yearController.view.bounds;
self.pageViewController.view.frame = pageViewRect;
viewCalendarMonth = [[SGMonthCalendarView alloc] initWithFrame:frame];
[self.view addSubview:viewCalendarMonth];
arrayCalendars = #[pageViewController.view, viewCalendarMonth];
}
-(UIViewController*)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController
{
NSDate *currentDate = [[SGSharedDate sharedManager] currentDate];
NSDateComponents *currentDateComp = [NSDate returnDateComponentsForDate:currentDate];
self.nextDate = [NSDate dateWithYear:currentDateComp.year+1 month:currentDateComp.month day:currentDateComp.day];
[[SGSharedDate sharedManager] setCurrentDate:nextDate];
{
//I m reloading viewController's view here to display the new set of data for the increamented date.
}
NSLog(#"After Dates====>%#", nextDate);
return viewController;
}
-(UIViewController*)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController
{
NSDate *currentDate = [[SGSharedDate sharedManager] currentDate];
NSDateComponents *currentDateComp = [NSDate returnDateComponentsForDate:currentDate];
nextDate = [NSDate dateWithYear:currentDateComp.year-1 month:currentDateComp.month day:currentDateComp.day];
[[SGSharedDate sharedManager] setCurrentDate:nextDate];
{
//I m reloading viewController's view here to display the new set of data for the decremented date.
}
NSLog(#"Before Dates====>%#", nextDate);
return viewController;
}
The UIPageController tends to cache ahead (and back) so when you swipe forward from view controller N , N+1 loads BUT so does N+2 silently so in the event you swipe twice your content is already loaded.
But since you have an invisible side-effect (incrementing date) in the delegate method you are getting hit by this.
My suggestion is to remove the dating side-effect from your delegate and bind the date code to the presented view controller somehow ( a delegate or a property ) then trap the didFinishAnimating method of the pageController.
- (void)pageViewController:(UIPageViewController *)pageViewController didFinishAnimating:(BOOL)finished previousViewControllers:(NSArray *)previousViewControllers transitionCompleted:(BOOL)completed {
if (completed) {
if ([[self.pageViewController.viewControllers firstObject] isKindOfClass:[MyCalenderViewController class]]) {
currentDate = [self workOutCurrentDateForVC:[self.pageViewController.viewControllers firstObject]];
}
}
}
Side effects are a code smell. Try and avoid.
EDIT
I just noticed you are recycling the view controller and reloading it with the new date point. Don't do this . You need to treat a view controller as a single page of your book. So generate a new one and set it up for the month...
Briefly as an example...
#interface YearViewController : UIViewController
#property NSDate *focusedYear;
-(instancetype)initWithYear:(NSDate)adate;
#end
#implementation YearViewController
-(instancetype)initWithYear:(NSDate)adate {
self = [super initWithNibName:nil bundle:nil];
if(self) {
self.focusedYear = adate;
....
}
}
#end
So in the page controller delegate you just serve up a new "page" based on the date that the previous one had (and the direction you are paging)
-(UIViewController*)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController
{
YearViewController *yvc = (YearViewController *)viewController;
NSDate *currentDate = yvc.focusedYear;
NSDateComponents *currentDateComp = [NSDate returnDateComponentsForDate:currentDate];
NSDate *nextDate = [NSDate dateWithYear:currentDateComp.year+1 month:currentDateComp.month day:currentDateComp.day];
YearViewController *freshYVC = [[YearViewController alloc] initWithDate:nextDate];
return freshYVC;
}
The comment about side effects still stands.

Restoring multiple text fields and labels in one view controller

I have a view controller containing 2 text fields, 2 segmented controls and a few labels which display time stamps set by the user using UIButtons. I would like to be able to restore any user set values for these items upon quit / restart, as well as when going back to my main menu view controller, which is a navigation controller. Using the following code, I am able to restore one of the text fields on background / terminate / restart, but I am unsure as to how to accomplish this for my other text field or the time stamp labels and segmented controls. I have tried to duplicate the restoration code with changes for the text field name and the #"UnsavedText" string with no luck.
Furthermore, whenever I go back to the main menu using the back button in the navigation bar, I lose all of my data from all fields.
Here is the code in my delegate, opting in to state restoration:
// Sets RESTORATION
-(BOOL)application:(UIApplication *)application shouldRestoreApplicationState:(NSCoder *)coder
{
return YES;
}
-(BOOL)application:(UIApplication *)application shouldSaveApplicationState:(NSCoder *)coder
{
return YES;
}
Here is the relevant code in my view controller to restore the one text field. I am including my viewDidLoad code with state initialization in case that is somehow part of the problem:
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
// Updates time on currentTimeLabel
[self updateTime];
// Sets initial button states
[self setInitialState];
// Sets UITextField delegate
self.startLevel.delegate = self;
self.stopLevel.delegate = self;
// initializes basic values for segmented controls
bigTank = YES;
startFractions = #"";
stopFractions = #"";
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
// Restoration of text fields
-(void)encodeRestorableStateWithCoder:(NSCoder *)coder
{
// start level text
[coder encodeObject:_startLevel.text forKey:#"UnsavedText"];
[super encodeRestorableStateWithCoder:coder];
}
-(void)decodeRestorableStateWithCoder:(NSCoder *)coder
{
// start level text
_startLevel.text = [coder decodeObjectForKey:#"UnsavedText"];
[super decodeRestorableStateWithCoder:coder];
}
// Sets initial button and label states
- (void)setInitialState
{
self.start.enabled = YES;
self.stop.enabled = NO;
self.calculate.enabled = NO;
self.resume.enabled = NO;
// Text for time labels reset
_startTimeLabel.text = #"- -:- -:- -.- -";
_stopTimeLabel.text = #"- -:- -:- -.- -";
// Resets the minuteRateLabel
_minuteRateLabel.text = #"--.--";
// Resets the text fields to their initial state with the placeholder "inches" text
[_startLevel setText:nil];
[_stopLevel setText:nil];
// Resets inch levels to EVEN
[self.startFractionControl setSelectedSegmentIndex:0];
[self.stopFractionControl setSelectedSegmentIndex:0];
startFractions = #"";
stopFractions = #"";
}
Thanks in advance!
EDIT:
This is the code I tried per the suggestion:
My button click should ostensibly save the time stamp value:
- (IBAction)startButton:(UIButton *)sender
{
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:#"hh:mm:ss.SS"];
_startTimeLabel.text = [formatter stringFromDate:[NSDate date]];
UIButton *startButton = (UIButton *)sender;
// Creates the start time stamp for use in the calculation
startTime = [NSDate timeIntervalSinceReferenceDate];
// sets button states
startButton.enabled = NO;
stop.enabled = YES;
// Hides keypad on startButton click
[self.startLevel resignFirstResponder];
[self.stopLevel resignFirstResponder];
// saves the value for restoration using user defaults
NSDateFormatter *startTimeSave = formatter;
[[NSUserDefaults standardUserDefaults]
setObject:startTimeSave forKey:#"startTimeSaver"];
}
You can save your text values using user defaults. Here is a sample code snippet:
NSString *valueToBeSaved = #"someValue";
[[NSUserDefaults standardUserDefaults]
setObject:valueToBeSaved forKey:#"preferenceName"];
And for retrieving your value later, use this:
NSString *savedValue = [[NSUserDefaults standardUserDefaults]
stringForKey:#"preferenceName"];

UITextView textViewDidChangeSelection called twice

What I have :
TextView
NSArray (string)
AVAudioplayer (not yet implemented)
When I select a word in TextView :
• Check if word exist in Array
• Start Audioplayer with associated sound
Unfortunately when I tap twice to select a word inside TextView, textViewDidChangeSelection is called twice. I don’t know why I see "Youpie" twice.
I just changed inputView to hide keyboard because I only need TextView to be used in selecting mode.
- (void)textViewDidChangeSelection:(UITextView *)tve;
{
NSString *selectedText = [tve textInRange:tve.selectedTextRange];
if(selectedText.length > 0)
{
for (NSString *text in textArray)
{
if ([selectedText isEqualToString:text])
NSLog(#"Youpie");
tve.selectedTextRange = nil;
if (ps1.playing == YES)
{
[self stopEveryPlayer];
[self updateViewForPlayerState:ps1];
}
else if ([ps1 play])
{
[self updateViewForPlayerState:ps1];
fileName.text = [NSString stringWithFormat: #"%# (%d ch.)", [[ps1.url relativePath] lastPathComponent], ps1.numberOfChannels, nil];
}
else
NSLog(#"Could not play %#\n", ps1.url);
break;
}
}
}
}
- (void)awakeFromNib
{
textArray = [[NSArray alloc] initWithObjects:#"dog",#"cat",#"person",#"bird",#"mouse", nil];
textView.inputView = [[[UIView alloc] initWithFrame:CGRectZero] autorelease];
textView.delegate = self;
// ...
}
I noticed something when I was double tapping on each good word in my text.
textViewDidChangeSelection
If a word is already selected and no action choosen, I have 1 "Youpie".
If not, I have 2 "Youpie".
I found a simple solution. I removed selectedRange after getting value. textViewDidChangeSelection called once.
What I have changed
tve.selectedTextRange = nil;
I use a subclass of UITextView to disable menu.
-(BOOL)canPerformAction:(SEL)action withSender:(id)sender
{
return NO;
return [super canPerformAction:action withSender:sender];
}
I added an implementation for AVAudioPlayer (ps1) too.
My "autoplay" is working if a known word is selecting :)
I don't have an answer for why the method gets called twice or how to prevent this, but an alternative solution might be to display an additional item in the edit menu that pops up in a text view when a word is double clicked. Then, your action for initiating a sound based on the word could be triggered from the action selector defined in that additional menu item. In this design, you'd remove your textViewDidChangeSelection and thus would not get called twice. See http://developer.apple.com/library/ios/#documentation/StringsTextFonts/Conceptual/TextAndWebiPhoneOS/AddingCustomEditMenuItems/AddingCustomEditMenuItems.html for some additional info about modifying the standard menu.

Resources