Prolong UIPickerView [selectRow: inComponent: animated:]'s animation - ios

I'm using a UIPickerView to display random numbers. The user can press a button, and thus trigger a random selection of a number inside the UIPickerView.
It does not matter how many objects or numbers are being displayed inside the UIPickerView, when I call the method:
[self.picker selectRow:randomRow inComponent:0 animated:YES];
It always gets animated with the same time interval.
Is there any option or method to prolong the animation time interval of above method?
I have tried placing it in an animation block:
[UIView beginAnimations:#"identifier" context:nil];
// code
[UIView commitAnimations];
but this seems to be a dead end.
I have also tried executing it in a completion blocks:
// 0.34f is the approximate defualt apple animation
[UIView animateWithDuration:0.34f animations:^{
[self.picker selectRow:randomRow inComponent:0 animated:YES];
} completion:^(BOOL finished) {
[UIView animateWithDuration:0.34f animations:^{
[self.picker selectRow:randomRow inComponent:0 animated:YES];
} completion:nil];
}];
Any help would be appreciated.

I made a project and play for a while till I find out this tricky solution. It base on the method performSelector:afterDelay
Here is the touch up inside code of your button:
- (void)click:(id)sender
{
int randomRow = <>;//Your random method
int currentRow = [_picker selectedRowInComponent:0];
int i = 0;
while(1)
{
i++;
NSString *rowToSelectString = [NSString stringWithFormat:#"%d", currentRow];
NSDictionary *rowToSelectDictionary = #{#"row":rowToSelectString};
if(randomRow < currentRow)
{
// Go backward
currentRow--;
}
else
{
// Go forward
currentRow++;
}
[self performSelector:#selector(selectRowInPicker:) withObject:rowToSelectDictionary afterDelay:i*0.1];//Change the delay as you want
if(currentRow == randomRow)
{
break;
}
}
}
And the trick:
-(void)selectRowInPicker:(NSDictionary *)rowToSelectDictionary
{
NSInteger row = [[rowToSelectDictionary objectForKey:#"row"] integerValue];
[_picker selectRow:row inComponent:0 animated:YES];
}
This work well for me. Tell me if you're having problem with it.

Finally, I have implemented a "thinner" version of the chosen answer:
- (IBAction)spinTheWheelButtonPressed:(UIButton *)sender
{
for (int i = 0; i < 30; i++) {
NSUInteger randomRow = arc4random_uniform((int)[self.dataSource count]);
[self performSelector:#selector(selectRowInPicker:) withObject:#(randomRow) afterDelay:i*0.1];
}
}
- (void)selectRowInPicker:(NSNumber *)randomRow
{
[self.picker selectRow:[randomRow integerValue] inComponent:0 animated:YES];
}

I had a similar problem , but unfortunately there is no easy way to delay the animation thats as far as I know.
So I went around and placed a UIView on top of the pickerView. I subclassed that view to pass all of its touches to the pickerView and when something was selected I just drew a layer with less opacity in the view where the row would usually come to rest and animated it in a animation block since the selection is always right at the middle of the pickerView.

Related

UIView moves down when Assigning Value to UITextField

I'm making an app that moves the view (when iPone 4s/5/5c/5s) up when the keyboard is shown. however, when i type something using the number pa, it triggers the view to go back down. The code for textField:shouldChangeCharactersInRange:replacementString changes the text and then assigns it back to the textbook.
- (BOOL) textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
NSString* totalString = [NSString stringWithFormat:#"%#%#",textField.text,string];
if(textField.tag==102) {
if (([textField.text isEqualToString:#"0"]) && (textField.text.length == 1)) {
textField.text = #"";
NSLog(#"Zero Encountered");
return NO;
}
if (range.length == 1) {
// Delete button was hit.. so tell the method to delete the last char.
textField.text = [_iOSApiService_model formatPhoneNumber:totalString deleteLastChar:YES];
} else {
textField.text = [_iOSApiService_model formatPhoneNumber:totalString deleteLastChar:NO];
}
return YES;
}
return YES;
}
-(void) keyboardWasShown:(NSNotification *)notification {
//The keyboard was shown, move movableView objects UP
NSLog(#"value of _keyboardshownUp is %i",_keyboardshownUp);
if (_keyboardshownUp == 1) {
//Keyboard was already shown
} else {
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.5];
//This will move the frame back to place
_movableView.frame = CGRectMake(_movableView.frame.origin.x,
_movableView.frame.origin.y+_variableMove,
_movableView.frame.size.width,
_movableView.frame.size.height);
[UIView commitAnimations];
NSLog(#"Move View up");
_keyboardshownUp = 1;
}
}
-(void) keyboardWillHide:(NSNotification *)notification {
//The keyboard was shown, move movableView objects down
NSLog(#"value of _keyboardshownUp is %i",_keyboardshownUp);
if (_keyboardshownUp == 0) {
//Keyboard was already hiden
} else {
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.5];
//This will move the frame back to place
_movableView.frame = CGRectMake(_movableView.frame.origin.x,
_movableView.frame.origin.y-_variableMove,
_movableView.frame.size.width,
_movableView.frame.size.height);
[UIView commitAnimations];
NSLog(#"Move View Down");
_keyboardshownUp = 0;
}
}
I'm doing this in conjunction with UIKeyboardWillHideNotification and UIKeyboardDidShowNotification. If i disable the textField:shouldChangeCharactersInRange:replacementString then there is no problem. but, the moment I assign a value to the "issue" UITextField, as if the keyboard was hidden (even though it is not) and the Main UIView reverts back to its orig position thus the Keyboard obscures the view. PLEASE HELP!!!
Recently i have facing these sort of problem too much, thats why i'm creating a library to face this situation. Take a look at KeyboardAnimator. Hope it met your requirement.
To moves your UI controller, when assigning value to UITextField use the "IQKeybordManager" a library to solved this situation.
IQKeybordManager ran into an issues where the iPhone keyboard slide up and cover the UITextField/UITextView. IQKeyboardManager allows you to prevent issues of the keyboard sliding up and cover UITextField/UITextView without needing you to enter any code and no additional setup required. To use IQKeyboardManager you simply need to add source files to your project.

Animation works if UITableView scrolled gently, doesn't work when scrolled fast

I'm blinking a label inside a custom UITableViewCell using this code:
-(void) startBlinker
{
[self stopBlinker];
self.edgeCaseLabelTimer = [NSTimer scheduledTimerWithTimeInterval:.5 target:self selector:#selector(blinkEdgeCaseLabel) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.edgeCaseLabelTimer
forMode:NSRunLoopCommonModes];
}
-(void) blinkEdgeCaseLabel
{
if (blinkCycle == 0)
{
[UIView transitionWithView:self.thisCustomCell.edgeCaseLabel duration:1.0 options:UIViewAnimationOptionTransitionCrossDissolve animations:^{
self.thisCustomCell.edgeCaseLabel.textColor = [UIColor clearColor];
} completion:nil];
blinkCycle = 1;
}
else if (blinkCycle == 1)
{
[UIView transitionWithView:self.thisCustomCell.edgeCaseLabel duration:1.0 options:UIViewAnimationOptionTransitionCrossDissolve animations:^{
self.thisCustomCell.edgeCaseLabel.textColor = [UIColor redColor];
} completion:nil];
blinkCycle = 0;
}
}
I expect there's more elegant and modern code around for this, but it works. At least it works as long as I don't scroll the TV hard. Gently, works fine. Fast, the label remains (when it comes to rest) in one (or the other) of the states and doesn't blink.
The only cells affected are the top and bottom cells (which are the only ones with blinking labels, not coincidentally).
I've tried using scrollViewDidEndScrollingAnimation and scrollViewDidEndDecelerating to bump it into action again, but no dice.
Any ideas?
Note: I'm testing this on simulator & not on a device.
Edit:
Following #Woodstock's suggestion I switched to a regular NSTimer. In addition, I incorporated this code:
-(BOOL)topOrBottomCellIsVisible
{
NSArray *indexes = [self.myTableview indexPathsForVisibleRows];
for (NSIndexPath *index in indexes)
{
if (index.row == 0)
{
return YES;
}
if (index.row == detailFRC.fetchedObjects.count - 1)
{
return YES;
}
}
return NO;
}
from #Devunwired, contributed in his answer to this question. I put this method in several critical locations related to appearing and scrolling.
Now things seem more robust, but I think #Fahim Parkar is probably onto something with it running on the sim instead of a device. I say "more robust" rather than "cured" because while the blinking seems more resistant to fast scrolling, it still doesn't start automatically upon initial appearance of the cell(s) in question. I'll report back when I try it on the device.

How to glow a UIButton randomly from a sequence of buttons continuously

I am working on a memory based matching puzzle game. For that I need to glow buttons randomly from sequence. I have been googling sample puzzles,games & code snippets since a week or so, yet I was unable to trace out an appropriate sample project or some source to get started.
I am glowing the lights(buttons) by changing its background images(imitation), I have already set the default images for buttons(coloured bulb when in stable state) in story board. I have used NSMutableArray of IBOutletCollection i.e. tapLightButtons. Here is what I tried to get the sequence of lights glowing in a random mode to get the game experience.
-(void)startGlowingLights
{
[self shuffleValuesInArray:_tapLightButtons];
[self shuffleValuesInArray:lightArray];
[self shuffleValuesInArray:_initialButtonImages];
[self performSelector:#selector(animateButtonSequence) withObject:self afterDelay:0.2];
}
- (void) animateButtonSequence
{
__block UIButton *tapLight;
[UIView animateWithDuration:0.15
delay:0.0
options:UIViewAnimationOptionCurveLinear
animations:^{
// button flash animation
} completion:^(BOOL finished) {
if (finished) {
if (i < _tapLightButtons.count) {
i++;
[self performSelector:#selector(animateButtonSequence) withObject:self afterDelay:1.0];
tapLight = [self.tapLightButtons objectAtIndex:i-1];
UIImage *glownLight = [UIImage imageNamed:[lightArray objectAtIndex:i-1]];
[tapLight setBackgroundImage:glownLight forState:UIControlStateNormal];
[[TTLMusicPlayer sharedInstance] playGlowLightSound:[[NSBundle mainBundle] pathForResource:#"beep" ofType: #"mp3"]];
//Reset the completed glowed button to previous state(off mode)
double delayInSeconds = 1.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
if (i != 0 && i < _initialButtonImages.count) {
[tapLight setBackgroundImage:[self.initialButtonImages objectAtIndex:i-1] forState:UIControlStateNormal];
}
});
}
else {
i = 0;
}
NSLog(#"i value is %d",i);
}
}];
}
-(NSMutableArray *)shuffleValuesInArray:(NSMutableArray *)shuffledArray
{
for (NSInteger x=0;x<[shuffledArray count];x++)
{
NSInteger randomNumber = (arc4random() % ([shuffledArray count] - x)) + x;
[shuffledArray exchangeObjectAtIndex:x withObjectAtIndex:randomNumber];
}
return shuffledArray;
}
tapLightButtons is the array which holds puzzled buttons(lights) which glows automatically one after another in random fashion.
lightArray is an array holding all the appropriate colour images(flash to produce glowed affect)
initialButtonImages contains array of all default(initial) images of buttons(off mode lights)
Some how I could partially achieve what I wanted to, surprisingly even after shuffling all arrays, still I see variations in the order because the glowed image should be the corresponding of stable(off mode coloured light) and then after resetting, the stable image should match the one before the light was actually glowed.
Posted the question on game dev site from Stack Exchange as well!!
Game Screen for better understanding
After flashing of coloured light
How to properly glow the buttons randomly from sequence?
I would do this a different way that leads to simpler code. I created an array of 9 custom buttons in IB and added them to an outlet collection. I subclassed the buttons, and added a flashWithOnTime: method, so the button itself would take care of switching between the normal and highlighted states.
This is the method in the button subclass,
-(void)flashWithOnTime:(CGFloat)onTime {
[self setHighlighted:YES];
[self performSelector:#selector(setHighlighted:) withObject:#NO afterDelay:onTime];
}
This is the code in the view controller that starts the sequence (from a button tap). Rather than shuffling, I pick a random index out of an array, and then delete that entry so it can't be picked again. The methods offImageWithSolidColor and onImageWithSolidColor, are just methods I used to create the images.
#interface ViewController ()
#property (strong, nonatomic) IBOutletCollection(RDFlashingButton) NSArray *buttons;
#property (strong,nonatomic) NSArray *indexes;
#property (strong,nonatomic) NSMutableArray *mutableIndexes;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.indexes = #[#0,#1,#2,#3,#4,#5,#6,#7,#8];
for (RDFlashingButton *aButton in self.buttons) {
[aButton setBackgroundImage:[self offImageWithSolidColor] forState:UIControlStateNormal];;
[aButton setBackgroundImage:[self onImageWithSolidColor] forState:UIControlStateHighlighted];
}
}
-(void)flashRandomly {
if (self.mutableIndexes.count > 0) {
int index = arc4random_uniform(self.mutableIndexes.count);
int value = [self.mutableIndexes[index] intValue];
RDFlashingButton *aButton = self.buttons[value];
[aButton flashWithOnTime:0.4];
[self.mutableIndexes removeObjectAtIndex:index];
[self performSelector:#selector(flashRandomly) withObject:nil afterDelay:.4];
}
}
-(IBAction)startFlashing:(id)sender {
self.mutableIndexes = [self.indexes mutableCopy];
[self flashRandomly];
}

call segue after genie effect

I am new to objective C, and I am trying to get into speed by doing some tutorial adding my own bit to it. I am working now with Genie effect from Ciechan / BCGenieEffect. I think I understand the implementation since it seems to work for me, however I have a question, How could I trigger a segue after the end of the animation.
The code i am using is as follow:
- (void) genieToRect: (CGRect)rect edge: (BCRectEdge) edge
{
NSTimeInterval duration = 0.97;
CGRect endRect = CGRectInset(rect, 5.0, 5.0);
[self.buttons enumerateObjectsUsingBlock:^(UIButton *button, NSUInteger idx, BOOL *stop) {
button.enabled = NO;
}];
if (self.viewIsIn) {
[self.draggedView genieOutTransitionWithDuration:duration startRect:endRect startEdge:edge completion:^{
self.draggedView.userInteractionEnabled = YES;
[self.buttons enumerateObjectsUsingBlock:^(UIButton *button, NSUInteger idx, BOOL *stop) {
button.enabled = YES;
}];
}];
} else {
self.draggedView.userInteractionEnabled = NO;
[self.draggedView genieInTransitionWithDuration:duration destinationRect:endRect destinationEdge:edge completion:
^{
[self.buttons enumerateObjectsUsingBlock:^(UIButton *button, NSUInteger idx, BOOL *stop) {
button.enabled = YES;
}];}];
}
self.viewIsIn = ! self.viewIsIn;
}
I put a button to call the animation:
- (IBAction)leftButtonTapped:(UIButton *)sender
{
[self genieToRect:sender.frame edge:BCRectEdgeTop];
}
Everything is working fine there, but now, I would like, when the animation ends, to call a segue to move to the next view (like Apple is doing in mail after you click for instance Trash - Once the mail arrive to the trash, the view change)
I tried to call a method inside the button method like this:
[leftButton addTarget:self action:#selector(prepareForSegue:sender:) forControlEvents:UIControlEventTouchUpInside];
Then I added my method like this:
- (void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
[self performSegueWithIdentifier:#"public" sender:self];
}
Unfortunately, it does not work...
Thank you in advance for any help.
performSegueWithIdentifier:sender will call prepareForSegue:identifier. If you have something to do there (like passing information to the next ViewController at the end of the segue), you put your code in prepareForSegue:identifier.
So, remove performSegueWithIdentifier:sender in prepareForSegue:identifier.
To perform the segue after the animation, put it in the block. Here, you have ...completion:^{}. You put there what you want to do a the completion. So call here performSegueWithIdentifier:sender.

iPhone NSTimer running single method

I'm new to programming for the iPhone and i'm having a problem with this function
-(IBAction)changeX {
[self timere:field1];
[self timere:field2];
}
this is a button to move an uitextfield object across the screen. The problem is i want to run this method on the first field , complete it then go on to the second. The timere function uses NSTimer to continously move the object until it reaches a certain point at which it terminates. I have two other functions shown below. The actual program im making is much longer but the objective is the same and that code is too long. The problem is running the first function then the second. Thank you for the help.
-(void)timere:(UITextField*)f1 {
UITextField*fielder1=f1;
NSInvocation*timerInvocation = [NSInvocation invocationWithMethodSignature:
[self methodSignatureForSelector:#selector(moveP:)]];
[timerInvocation setSelector:#selector(moveP:)];
[timerInvocation setTarget:self];
[timerInvocation setArgument:&fielder1 atIndex:2];
timer1 = [NSTimer scheduledTimerWithTimeInterval:0.03 invocation:timerInvocation repeats:YES];
}
-(void) moveP:(UITextField*)f1 {
UITextField*fielder1=f1;
fielder1.center = CGPointMake(fielder1.center.x+4, fielder1.center.y);
if (fielder1.center.x>237) {
[timer1 invalidate];
}
}
The standardized method of animating UIView objects is by using methods such as the following.
+ animateWithDuration:delay:options:animations:completion:
The syntax for these methods might be a bit daunting if you are unfamiliar with Objective-C Blocks. Here's a usage example which moves the first field, then the second field afterwards.
[UIView animateWithDuration:0.3 animations:^{
[field1 setCenter:CGPointMake(FINAL_CENTER_X1, FINAL_CENTER_Y1)];
} completion:^(BOOL finished) {
[UIView animateWithDuration:0.3 animations:^{
[field2 setCenter:CGPointMake(FINAL_CENTER_X2, FINAL_CENTER_Y2)];
}];
}];
EDIT: For a general fix on your specific problem, I would make the following modifications:
-(IBAction)changeX {
[self timere:field1];
}
-(void) moveP:(UITextField*)f1 {
UITextField*fielder1=f1;
fielder1.center = CGPointMake(fielder1.center.x+4, fielder1.center.y);
if (fielder1.center.x>237) {
[timer1 invalidate];
// I'm really not sure about the scope of field1 and field2, but
// you can figure out the encapsulation issues
if (f1 == field1) {
[self timere:field2];
}
}
}
A more generic and indeed low-level solution would be to have a state variable. Perhaps you would have some sort of NSArray *fields of UITextField * and int state = 0. Where I added the conditional in moveP above, you would state++ and call [self timere:[fields objectAtIndex:state]] if state < [fields count]. You timer invalidation code is correct, anyway.

Resources