Here's my setup: I want to programmatically...
Raise the built-in keyboard (by giving focus to a text field)
Programmatically animate a finger graphic moving over the keyboard.
Fake a tap on the keyboard and have the keyboard respond to that tap.
The use-case is to create a video showing "look, you can type, and this is what happens...", but with a stylized and partially transparent finger-image, rather than somebody's actual finger blocking the view.
Related: I can map the X/Y coordinates for my video easily enough, but bonus points if anyone can help me get the position of a given key on the keyboard, thus simplifying my writing of the routine fakeTypeText: #"Hello World!" to move the point of the finger to the correct location.
Thanks!
EDIT: Use case sample code...
- (void) fakeTypeText: (NSString*) text
{
int len = (int) text.length;
for (int ii = 0 ; ii < len ; ++ii)
{
char oneChar = [text characterAtIndex: ii];
// *** BONUS POINTS ***
CGPoint kbPoint = CGPointZero; // ?!?! Point on keyboard of the oneChar-key.
[UIView animateWithDuration: duration
animations: ^{
self.fingerView.center = kbPoint;
}
completion: ^(BOOL finished) {
// *** MAIN QUESTION ***
[self fakeTapEventOnKeyboard: oneChar]; // ?!?! How to do this?
}
];
}
}
EDIT: To clarify further, the behaviour I want is, when I call fakeTapEventOnKeyboard, I want iOS to behave as if the user had tapped that key. Specifically, the key should highlight (including the little popup-key graphic), make the key-tapped sound and type the key into the firstResponder field, if any.
Related
I'm a beginner at objective C learning to program, and also beginner at asking questions on this site, please bear with me.
I am currently trying to draw a column of boxes (UIControls) on the screen, and be able to scroll them upward or downward infinitely. So when one goes off the bottom of the screen its shifted to the bottom and reused.
I know there must be a lot of mistakes in the code. But the gist of what I am trying to do is: The boxes are all in an array (imArray). When a box scrolls off the bottom of the screen its taken off the end of the array, and inserted at the beginning. Then the box inserts itself graphically into the top of the column.
The first if statement deals with scrolling off the bottom of the screen, and it works fine. But the second if statement, where i try to do the opposite with similar code works only when i scroll slowly, when i scroll quickly the spacing between boxes becomes uneven, and sometimes a box just locks up on the screen and stops moving.
Any help is appreciated, and I will try to provide any more clarity that may be needed.
-(BOOL)continueTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event
{
CGPoint pt = [touch locationInView:self];
int yTouchEnd = pt.y;
int yTouchChange = yTouchEnd - yTouchStart;
//iterate through all boxes in imArray
for(int i = 0; i < self.numberOfSections; i++)
{
//1. get box
STTimeMarker *label = self.imArray[i];
//2. calculate new label transform
label.transform = CGAffineTransformTranslate(label.startTransform, 0, yTouchChange);
CGRect frame = label.frame;
//3. if the box goes out of the screen on the bottom
if (frame.origin.y > [[UIScreen mainScreen]bounds].size.height)
{
//1. move box that left the screen to to beginning of array
[self.imArray removeObjectAtIndex:i];
[self.imArray insertObject:label atIndex:0];
//2. get y value of box closest to top of screen.
STTimeMarker *labelTwo = self.imArray[1];
CGRect frameTwo =labelTwo.frame;
//3. put box that just left the screen in front of the box I just got y value of.
frame.origin.y = frameTwo.origin.y - self.container.bounds.size.height/self.numberOfSections;
label.frame=frame;
}
//1. if the box goes out of the frame on the top
// (box is 40 pixels tall)
if (frame.origin.y < -40)
{
[self.imArray removeObjectAtIndex:i];
[self.imArray addObject:label];
STTimeMarker *labelTwo = self.imArray[self.numberOfSections-1];
CGRect frameTwo =labelTwo.frame;
frame.origin.y = frameTwo.origin.y + self.container.bounds.size.height/self.numberOfSections;
label.frame=frame;
}
}
return YES;
}
If I understand what you are trying to do correctly, I think you want to come at this a different way. Your data model (the array) does not need to change. All that is changing as you scroll is the view, what is displaying on screen. The simplest way to achieve the appearance of an infinite scroll would be to use a UITableView and give it some large number of cells. Then your cellForRowAtIndexPath: method will return the cell for the correct position using the mod operator (%). Untested code:
- (NSInteger)tableView:(UITableView*)tableView numberOfRowsInSection:(NSInteger)section {
return 99999;
}
- (UITableViewCell*)tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath {
NSInteger moddedRow = indexPath.row % [self.imArray count];
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kSomeIdentifierConst forIndexPath:[NSIndexPath indexPathForRow:moddedRow inSection:indexPath.section]];
return [self configureCellWithData:self.imArray[moddedRow]];
}
This may not be sufficient for your purposes if you need true infinite scroll, but should work for most purposes.
I'm having a problem with side scrolling in Cocos2d. What the situation is, is that i have a sprite that contains multiple other sprites know as actions. The user can swipe back and forth horizontally to scroll through the multiple actions. Whats happening now is that it is very jerky and seems to lag and not a smooth scroll but just very choppy. Not sure what the problem is, I've tried to change the time of the animation but that doesn't seem to work.
- (void)translateInventoryForSwipe:(int)xTranslationValue {
NSArray* tempArray = [NSArray arrayWithArray:self.slotsCenterCoordinates];
[self.slotsCenterCoordinates removeAllObjects];
for (NSNumber* i in tempArray) {
NSNumber* newXCoordinate = [NSNumber numberWithInt:[i intValue] + xTranslationValue];
[self.slotsCenterCoordinates addObject:newXCoordinate];
}
[self updatePositionOfActionsInInventory];
}
this method takes in the delta x of the two touches from the parent view. (current touch minus previous touch) This sets the centre coord of all the actions in the scrolling view.
- (void)updatePositionOfActionsInInventory {
for (int inventoryCounter = 0; inventoryCounter < self.inventorySize; inventoryCounter++) {
FFAction* action = [self.actions objectAtIndex:inventoryCounter];
if (action != self.actionBeingDragged)
[self placeAction:action atIndex:inventoryCounter];
}
self.tempAction = nil;
}
- (void)placeAction:(FFAction*)action atIndex:(int)index {
const float yCenterCoordinate = self.boundingBox.size.height/2;
NSNumber* xCenterCoordinate = [self.slotsCenterCoordinates objectAtIndex:index];
CGPoint centerPointForActionAtIndex = ccp([xCenterCoordinate floatValue], yCenterCoordinate);
CCAction* updatePositionAction = [CCMoveTo actionWithDuration:0.03f position:centerPointForActionAtIndex];
if ([action.view numberOfRunningActions] == 0 || self.tempAction == action) {
[action.view runAction:updatePositionAction];
[action.view released];
}
}
this part is from the parent sprite that handles the touch:
CGPoint currentTouch = [self convertTouchToNodeSpace:touch];
CGPoint previousTouch = [touch previousLocationInView:[touch view]];
int translationPoint = currentTouch.x - previousTouch.x;
[self.inventory translateInventoryForSwipe:translationPoint withPoint:currentTouch];
this then sets the action coordinate mimicking a scrolling effect. I'm not sure where its causing the jerky motion but if anyone has any help on the situation it would be awesome!
Assuming all of the complexity in your code is not required, there are several aspects to consider here, I'll go through them one by one.
First, memory allocation is expensive and a lot of it is done in every call of translateInventoryForSwipe:. A whole new NSArray is created and the self.slotsCenterCoordinates is repopulated. Instead, you should iterate the action sprites and reposition them one by one.
This brings us to the second aspect, which is the use of CCAction to move the sprites. A new CCAction is created for every sprite, again causing delay because of the memory allocation. The CCAction is created, even if it would not be used. Also, the use of actions might be the main cause of the lag as a new action won't be accepted until the previous has finished. A better way to do this would be to directly reposition the sprites by delta instead of assigning actions for repositioning. The action is not required to get smooth movement as the frequency of calls to translateInventoryForSwipe: will be high.
You should also consider using float to send the delta value to the method instead of int. The touch coordinates are floats and especially on retina devices this matters as the distance of two pixels is 0.5f.
Based on these aspects, here is a template of what a fixed method could look like. This is not tested, so there may be errors. Also, I assumed that action.view is the actual sprite, as the actions are assigned there.
- (void)translateInventoryForSwipe:(float)xTranslationValue {
for (FFAction *action in self.actions) {
if (action == self.actionBeingDragged)
continue;
// Position the items manually
float xCoordinate = action.view.position.x + xTranslationValue;
float yCoordinate = self.boundingBox.size.height/2;
action.view.position = ccp(xCoordinate, yCoordinate);
}
}
I am using content editable html loaded in uiwebview. I need the code to set the cursor position when the keyboard is hidden/shown.
Currently when i click on webview keyboard comes up but the content gets hidden behind the keyboard. Same happens when i keeps pressing on return key the cursor/text goes behind the webview or is not visible.
For head start, i need the functionality like used in iPad Evernote application. In that you can see cursor never goes behind the keyboard, it always starts above the keyboard.
I'm using javascript for this. I'm using a class to keep the code little more organized (so you'll see some this in the code), but that's not necessary.
// this is used to get the current coordinates of the selection - not very efficient, so it shouldn't be called too often
this.updateOffset = function() {
try{
var sel = window.getSelection();
range = sel.getRangeAt(0);
if(this.tmpSpan==null){
this.tmpSpan = document.createElement('span');
}
range.insertNode(this.tmpSpan);
this.yOffset = this.tmpSpan.offsetTop;
this.xOffset = this.tmpSpan.offsetLeft;
this.tmpSpan.parentNode.removeChild(this.tmpSpan);
}
catch(exc){
log('updateOffset:' + exc.toString());
}
}
// eContent is the div with 'contenteditable', while visibleHeight is an int, set from objective-c (depending on where the webview is positioned, keyboard height and screen height)
this.scrollToVisible = function(){
try {
if(this.eContent.clientHeight>this.visibleHeight){
this.updateOffset();
if(this.yOffset<window.pageYOffset){
window.scrollTo(0, this.yOffset);
}
else if(this.yOffset-window.pageYOffset>this.visibleHeight){
window.scrollTo(0, this.yOffset-this.visibleHeight);
}
}
}
catch (exc){
log('scrollToVisible: ', exc.toString());
}
}
In objective-c I'm setting the visibleHeight during keyboard showing up, and afterwards call scrollToVisible when keyboard has finished showing.
-(void)setVisibleHeight:(int)height{
[self stringByEvaluatingJavaScriptFromString:[NSString stringWithFormat:#"docState.visibleHeight=%d", height]];
}
-(void)scrollToVisible{
[self stringByEvaluatingJavaScriptFromString:#"docState.scrollToVisible()"];
}
scrollToVisible is also called on the javascript events: onkeyup, onpaset, oncut, which fixes the issue when pressing 'return' or wrapping on multiple lines.
In case you decide to go this way, you'll need to be very careful when you scroll through javascript, otherwise it may cause some issues with UIWebview control (e.g: placing the cursor at the wrong positions, moving cursor automatically on the top of the document etc.)
Edit
Some clarification regarding the visibleHeight. From what I can remember, I used this because I wasn't able to get the actual visible height from javascript (document.body.clientHeight would also include the area behind the keyboard).
Since I'm presenting the UIWebView in full screen, I'm setting the visible height as follows:
- (void)keyboardWillShow:(NSNotification *)notification {
...
NSDictionary *userInfo = [notification userInfo];
NSValue* aValue = [userInfo objectForKey:UIKeyboardFrameEndUserInfoKey];
CGRect keyboardRect = [aValue CGRectValue];
CGRect kbRect = [self.window convertRect:keyboardRect fromView:nil];
_kbRect = kbRect;
CGPoint sorigin = [self.superview convertPoint:self.frame.origin toView:nil];
int visibleHeight = _kbRect.origin.y-sorigin.y-_tlbInputAccessory.frame.size.height-lkPadBottom; // _tlbInputAccessory is a custom accessory view
[self stringByEvaluatingJavaScriptFromString:[NSString stringWithFormat:#"docState.setVisibleHeight(%d)", height]];
...
}
Note that I'm calling this from a subclass of UIWebView, so self will represent the UIWebView control.
I have a series of UIButtons which I use to update a couple of UILabels and initiate animation of an object from offscreen to onscreen. The progression goes like this. Button 1 & 2 are visible and when the user pushes a button a UILabel updates and a new UIImageView animates onto the screen. This works fine the first iteration, on round two, buttons 3 and 4 which were hidden, become visible. Once the user selects either button 3 or 4, another UIImageView should animate onto the screen (on top of the other images... "stacking" them). What is happening is the UIImageView which animated on after the user pressed either button 1 or 2, disappears, and nothing animates. This only occurs if the UILabels have to update based on the button selection. In the UILabel doesnt need to update, the animation works fine.
Relevent Code:
-(IBAction)didTapSizeButton:(id) sender {
if ([sender tag] == 1) {
sizeLabel.text = #"12 oz";
}
else if ([sender tag] == 2) {
sizeLabel.text = #"24 oz";
}
if (coasterTwo.center.x != 150) {
[UIView animateWithDuration:0.35
delay:0
options:(UIViewAnimationOptionCurveEaseOut)
animations:^{
coasterTwo.center = CGPointMake(150, 220);
} completion:^(BOOL finished) {
}];
}
containerOne.hidden = NO;
containerTwo.hidden = NO;
}
-(IBAction)didTapContainerButton:(id)sender {
if ([sender tag] == 3) {
containerLabel.text = #"Bottle";
}
else if ([sender tag] == 4) {
containerLabel.text = #"Can";
}
if (coasterTwo.center.x == 150 && coasterThree.center.x != 150) {
[UIView animateWithDuration:0.35
delay:0
options:(UIViewAnimationOptionCurveEaseOut)
animations:^{
CGPoint center = coasterThree.center;
center.x += 305;
coasterThree.center = center;
}
completion:^(BOOL finished){
}];
}
}
The UIImageViews are obviously reset to their starting positions as soon as I reach back to update the UILabel... Im just not sure how to prevent that.
I would recommend using the static UIView commitAnimations method because you don't need the completion block:
[UIView beginAnimations:nil context:nil];
//Code for changing frames / alpha / anything else that is animatable
[UIView commitAnimations];
Try this out, see if it helps.
There's got to be something simple. As I said, the code for didTapContainerButton seems suspect (everytime you hit it coasterThree will be shifted another 305 points to the right), but there's nothing shown above that would cause coasterTwo to disappear. The only thing that would do that is if (a) some IB link is messed up (though it's not clear how that could result in images from disappearing when you hit button 3 or 4); (b) there's code you're not showing which might hide it; or (c) you've got some other control on your window that is hiding it (I've definitely seen situations where updating a label caused it to be brought to the front, obscuring something behind it ... you can diagnose that by making the label have a transparent background, explicitly move the image to the foreground, or try setting alphas to 0.5 so you can see through stuff while you debug).
To diagnose the first possibility, you'd have to upload your project to DropBox or something and we could take a look at it (if you're confortable doing that ... if not, make a copy of your project, get rid of anything confidential/proprietary, and then upload that).
To diagnose the second possibility, you could just update your question with the full code of the view controller's .m file.
To diagnose this third possibility, you could either make sure that before your animation of an image that you move it up front (before you animate coasterThree, do [self.view bringSubviewToFront:coasterThree];). You could also make sure that the backgroundColor of all of your controls (and if you have any container UIViews that you've added in IB, them, too) are transparent [UIColor clearColor]. You could also, just for diagnostic purposes, temporarily reduce the alpha so you can see through them (and thus if anything is blocking something else (you can do this in IB, click on the main UIView background, press command+A to select all of the controls, and change alpha to 0.5).
Bottom line, I'd pursue option 3, and if you don't have luck, either share your project or share a comprehensive code sample. If you don't feel comfortable doing that, I might suggest that you throw away your old NIB and rebuild it from scratch (in case there's something weird in the one you've got) or start building a whole new test project where you incrementally re-implement your UI and see if you can figure out where it went wrong.
But there's nothing inconsistent with UIImageViews animating and setting UILabel text value. Down below I have code with two buttons, two images that animate on and off, and a label that's updated, and it all works fine. (I had a crisis of confidence: I was starting to fear that there was some weird interaction with UILabels and animation.) Sigh.
- (void)viewDidLoad
{
[super viewDidLoad];
CGPoint center;
self.label.text = #"";
// move image 1 (dog image) off screen to the left
center = self.image1.center;
center.x = -0.5 * self.view.frame.size.width;
self.image1.center = center;
self.image1.hidden = YES;
// move image 2 (cat image) off screen to the right
center = self.image2.center;
center.x = 1.5 * self.view.frame.size.width;
self.image2.center = center;
self.image2.hidden = YES;
// Do any additional setup after loading the view.
}
// if you hit button 1 (dog) button, change label, and animate the dog image on and cat image off
- (IBAction)button1:(id)sender
{
self.label.text = #"Dog";
self.image1.hidden = NO;
[UIView animateWithDuration:0.5
animations:^{
CGPoint center;
// move image 1 (dog image) onscreen
center = self.image1.center;
center.x = 0.5 * self.view.frame.size.width;
self.image1.center = center;
// move image 2 (cat image) off screen to the right (if it's not there already)
center = self.image2.center;
center.x = 1.5 * self.view.frame.size.width;
self.image2.center = center;
}];
}
// if you hit button 2 (cat) button, change label, and animate the dog image off and cat image on
- (IBAction)button2:(id)sender
{
self.label.text = #"Cat";
self.image2.hidden = NO;
[UIView animateWithDuration:0.5
animations:^{
CGPoint center;
// move image 1 (dog image) off screen to the left
center = self.image1.center;
center.x = -0.5 * self.view.frame.size.width;
self.image1.center = center;
// move image 2 (cat image) on screen
center = self.image2.center;
center.x = 0.5 * self.view.frame.size.width;
self.image2.center = center;
}];
}
Same thing just happened to one of my students. To prove you're not going crazy... try running it in iOS 5.x simulator ( and hopefully observe the desired behavior).
For iOS 6, you'll either have to code some state preservation for your UIView -- or -- if you haven't grown to love 'em and think you can't live without 'em -- get rid of the constraints in the xib file by unchecking "Uses Autolayout" in the file inspector.
Hope this helps!
I have a collection of six seat objects (UIViews with the alpha property set to 0) on my screen and I have player objects basically placed on top of them. The seats may or may not have a player on top of it. What I have right now is I've programmed the player's touchesMoved event so that when I drag a player on top of a seat object the seat's alpha property will go from 0 to 0.6. And then while still dragging the player, if I drag him off the seat the alpha property will go back to 0.
Instead, is there a built in UIView animation that could instead cause the alpha property to kind of fluctuate back and forth between .6 and .2? Kind of a throbbing effect? Would this require core animation or something more advanced?
I'm using the following in the Player's touchesMoved method to drag a player and to detect if it's above a seat:
UITouch *aTouch = [touches anyObject];
self.center = [aTouch locationInView:[self.superview]];
Seat *seat = [controller seatAtPoint:[aTouch locationInView:self.superview]];
if (seat) {
self.hoverSeat = seat;
seat.alpha = .6;
} else {
self.hoverSeat.alpha = 0;
}
The seatAtPoint method in my controller is as follows:
- (Seat *) seatAtPoint:(CGPoint)point {
NSMutableArray seats = [NSMutableArray arrayWithCapacity:6];
for (int i = 1; i <= 6; i++) {
Seat *aSeat = (Seat*)[self.view viewWithTag:i];
[seats addObject:aSeat];
}
for (Seat *seat in seats) {
if (CGRectContainsPoint([seat frame], point)) {
return seat;
}
}
return nil;
}
I use a hoverSeat ivar to hold the seat above which the player is hovering. And then if the seat returned is nil then it sets that seat's alpha to 0.
A bug I'm seeing with this code is if I move the player around the screen a little too quickly sometimes the alpha property won't go back to 0. Can anyone think of a more effective way to ensure that it goes back to 0?
Thank you for any suggestions.
I would look into using CoreAnimation; it's not that hard to use. Here's what it might look like for a fade:
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:.5];
seat.alpha = 1;
[UIView commitAnimations];
You can look into the callbacks that methods like these use to create some kind of pulsing animation ( Trigerring other animation after first ending Animation (Objetive-C) ) when combined with a condition you can probably set it up to loop forever. Also of note is that UIView Animations disable user input when they're running ( Infinitely looping animation ).
As for the bug you mentioned, if it's still happening, you could set up a timer that when fired, iterates through all the seats and checks for ones that aren't being displayed correctly. You might want to set this interval to be pretty infrequent so that it doesn't interrupt or impact the performance of your other animations.