React-Native iOS accurate active input position - ios

I have tried to use some the existing solution for iOS keyboard avoiding but none of the existing solutions seems to be working properly with some of my rather complicated views and I am now having to implement my own system. Thus far I have managed to come up with the following which works quite good:
_keyboardDidShow(e) {
const keyboardHeight = e.endCoordinates.height;
const windowHeight = Dimensions.get('window').height;
const keyboardTop = windowHeight - keyboardHeight;
const margin = 10;
const currentlyFocusedField = TextInput.State.currentlyFocusedField();
UIManager.measureInWindow(currentlyFocusedField, (x, y, width, height) => {
const fieldBottom = y + height;
const offset = fieldBottom - keyboardTop + 10;
if (offset > 0) {
this.setState({ offsetY: -1 * offset, offsettingKeyboard: true });
}
});
}
I should mention that these inputs are inside ScrollViews which are themselves in a bunch of other stuff.
The problem is that when the handler is called for a TextInput that has not been scrolled up inside its view, the proper "fieldBottom" position is obtained without adding the height while the height addition is needed for the real position of a field inside a view that has been scrolled.
I cannot decide if this is a bug or if I am missing something. Is there perhaps a way I can check if the input's parent has been scrolled?

Related

Appium - Horizontal Scroll view

I am using an android app that has horizontal scroll view, how to traverse through each of these items inside the horizontal scroll view using appium . The items change dynamically, so using textview won't work. Is there any method to get the length of the horizontal scroll view and traverse using loop?
Here is how i deal with a similar situation:
assuming you want to scroll down to a specific textView element.
while(!driver.findElement(By.id("textViewId").isdisplayed){
scrollDownManually();
}
public void scrollDownManually() {
Dimension size = Android.driver.manage().window().getSize();
int starty = (int) (size.height * 0.80);
int endy = (int) (size.height * 0.20);
int startx = size.width / 2;
Android.driver.swipe(startx, starty, startx, endy, 1000);
}
You can always perform swipe actions
TouchAction t=new TouchAction(driver);
//long press, for atleast 1 sec first and move the object
WebElement First=driver.findElementByXPath("//*[**First Element**]");
WebElement second=driver.findElementByXPath("//*[**Second Element**]");
t.longPress(longPressOptions().withElement(element(First)).withDuration(ofSeconds(3))).moveTo(element(second)).release().perform();

KonvaJS - Attach fixed scrollbar to element with dynamic height

Please assist, i have created dynamic content and i need attach scrollbar to it.
Main goal: when scroller reach to bottom my dynamic content bottom must be synchronized with it. (bottom of color boxes must be inline with the scroller)
Scroller size must be the same and don't change height.
How it will be work: if we have 12 boxes for example, slow speed will be used while scroller reach from top to bottom. if 30 boxes for example, it will be faster and so on...
i think so...
Maybe you provide a better solution.
"create dynamic elements layer" section creates color boxes, please check with different quantity
Thank you
jsFiddle
/* move scrollbar and dynamic elements */
verticalBar.on('dragmove', function (e) {
if (verticalBar.x() != verticalBarBottom.x()) {
verticalBar.x(verticalBarBottom.x())
}
if (verticalBar.y() < verticalBarBottom.y()) {
verticalBar.y(verticalBarBottom.y());
}
if (verticalBar.y() > verticalBarBottom.y() + verticalBarBottom.height() - verticalBar.height()) {
verticalBar.y(verticalBarBottom.y() + verticalBarBottom.height() - verticalBar.height());
}
productsOffset = verticalBar.y() - verticalBarBottom.y();
productsStep = products.getClientRect().height / verticalBarBottom.height();
productsStep = products.getClientRect().height / (verticalBarBottom.y() + verticalBarBottom.height());
newYpos = verticalBarBottom.y() - productPadding;
newYpos -= productsOffset * productsStep;
products.y(newYpos);
products.draw();
})
First, you may need to identify how far your left scrollbar is scrolled. I used progress variable (0 <= progress <= 1):
var progress = (verticalBar.y() - verticalBarBottom.y()) / (verticalBarBottom.height() - verticalBar.height());
Then you may need to identify what is min y and max y that layer may take to fit into you required view:
var minY = verticalBarBottom.y() + verticalBarBottom.height() - products.getClientRect().height;
var maxY = verticalBarBottom.y();
You may need to adjust these variables of your use case.
So real y is just somewhere between minY and maxY adjusted by progress:
var y = minY + (1 - progress) * (maxY - minY);
products.y(y);
Demo: https://jsfiddle.net/ehtro20j/

Graphics drawing code generates blank images, only on iOS

My app displays some images that I created using Image.createImage(). In some cases, the images are completely blank, but only on iOS. The images work fine on Android. Also, I create several images using Image.createImage() and most of them work fine. I don't see any difference between those and these.
To reproduce, run the enclosed app on both Android and iOS. The app shows two images. The second one is taken from the bottom half of the first one. On Android, the images show up fine. On iOS, the images show up for a few seconds, then vanish. It turns out that they only show up while iOS is displaying the startup screen. Once it switches to the actual app, the images are blank, although they take up the same space. Further tests reveal that the images are the correct size but are filled with transparent pixels.
I should say that, in my actual application, the images scale with the size of the screen, and are colored according to a user preference, so I can't just load them from a resource.
(BTW Notice the change I made to the stop method. This is unrelated but worth mentioning.)
Here's the test case:
import com.codename1.ui.Component;
import com.codename1.ui.Container;
import com.codename1.ui.Display;
import com.codename1.ui.Form;
import com.codename1.ui.Dialog;
import com.codename1.ui.Graphics;
import com.codename1.ui.Image;
import com.codename1.ui.Label;
import com.codename1.ui.layouts.BorderLayout;
import com.codename1.ui.layouts.BoxLayout;
import com.codename1.ui.plaf.UIManager;
import com.codename1.ui.util.Resources;
import com.codename1.io.Log;
import com.codename1.ui.Toolbar;
import java.util.Arrays;
/**
* This file was generated by Codename One for the purpose
* of building native mobile applications using Java.
*/
#SuppressWarnings("unused")
public class HalfImageBug {
private Form current;
private Resources theme;
public void init(Object context) {
theme = UIManager.initFirstTheme("/theme");
// Enable Toolbar on all Forms by default
Toolbar.setGlobalToolbar(true);
}
public void start() {
if (current != null) {
current.show();
return;
}
Form hi = new Form("Hi World", new BorderLayout());
hi.addComponent(BorderLayout.CENTER, makeComponent());
hi.show();
}
public void stop() {
current = Display.getInstance().getCurrent();
// This was originally if, but it should be while, in case there are multiple layers of dialogs.
while (current instanceof Dialog) {
((Dialog) current).dispose();
current = Display.getInstance().getCurrent();
}
}
public void destroy() {
}
private Component makeComponent() {
final Container container = new Container(new BoxLayout(BoxLayout.Y_AXIS));
container.setScrollableY(true);
container.add(new Label("Full Image:"));
Image fullIcon = createFullImage(0x44ff00, 40, 30);
Label fullImage = new Label(fullIcon);
container.add(fullImage);
container.add(new Label("---"));
container.add(new Label("Half Image:"));
Image halfIcon = createHalfSizeImage(fullIcon);
Label halfImage = new Label(halfIcon);
container.add(halfImage);
return container;
}
private Image createFullImage(int color, int verticalDiameter, int horizontalRadius) {
// Make sure it's an even number. Otherwise the half image will have its right and left halves reversed!
int diameter = (verticalDiameter / 2) * 2;
final int iconWidth = 2 * horizontalRadius;
int imageWidth = iconWidth + 2;
int imageHt = diameter + 2;
Image fullImage = Image.createImage(imageWidth, imageHt);
Graphics g = fullImage.getGraphics();
g.setAntiAliased(true);
g.setColor(color);
g.fillRect(0, 0, imageWidth, imageHt);
g.setColor(darken(color, 25));
g.fillArc(1, 1, iconWidth, diameter, 180, 360);
g.setColor(0xbfbfbf);
final int smallerHt = (9 * diameter) / 10;
g.fillArc(0, 0, iconWidth, smallerHt, 180, 360);
Image maskImage = Image.createImage(imageWidth, imageHt);
g = maskImage.getGraphics();
g.setAntiAliased(true);
g.setColor(0);
g.fillRect(0, 0, imageWidth, imageHt);
g.setColor(0xFF);
g.fillArc(1, 1, iconWidth, diameter, 180, 360);
fullImage = fullImage.applyMask(maskImage.createMask());
return fullImage;
}
private Image createHalfSizeImage(Image fullImage) {
int imageWidth = fullImage.getWidth();
int imageHt = fullImage.getHeight();
int[] rgbValues = fullImage.getRGB();
// yeah, I've since discovered a much more sensible way to do this, but it doesn't fix the bug.
int[] bottomHalf = Arrays.copyOfRange(rgbValues, rgbValues.length / 2, rgbValues.length);
//noinspection StringConcatenation
Log.p("Cutting side image from " + imageWidth + " x " + imageHt + " to " + imageWidth + " x " + (imageHt / 2));
return Image.createImage(bottomHalf, imageWidth, imageHt / 2);
}
private static int darken(int color, int percent) {
if ((percent > 100) || (percent < 0)) {
throw new IllegalArgumentException("Percent out of range: " + percent);
}
int percentRemaining = 100 - percent;
return (darkenPrimary((color & 0xFF0000) >> 16, percentRemaining) << 16)
| (darkenPrimary((color & 0xFF00) >> 8, percentRemaining) << 8)
| (darkenPrimary(color & 0xFF, percentRemaining));
}
private static int darkenPrimary(int primaryValue, int percentRemaining) {
if ((primaryValue < 0) || (primaryValue > 255)) {
throw new IllegalArgumentException("Primary value out of range (0-255): " + primaryValue);
}
return (primaryValue * percentRemaining) / 100;
}
}
This is discussed in this issue.
Generally the images initially appear because of the screenshot process that shows them so they never really show up on iOS natively.
A common cause for these issues is creating images off of the EDT which doesn't seem to be the issue in this specific code.
It's hard to see what is going on so I guess we'll need to evaluate the issue.
Here's a workaround. This works, but doesn't anti-alias very well. It will do until the iOS code gets fixed.
The problem is as described elsewhere. The Graphics.fillArc() and drawArc() methods work fine on Android, but often fail on iOS. Here's the behavior:
if width == height, they correctly draw a circle.
if width < height, they should draw an ellipse, but they draws a circle, centered over the intended ellipse, with a diameter equal to width.
if width > height, they draw nothing.
The workaround draws a circle against a transparent background, then draws that circle, squeezed in one direction to an ellipse, into the proper place. It doesn't do a very good job of anti-aliasing, so this is not a good substitute for working code, but it will do until the bug gets fixed. (This workaround handles fillArc, but it shouldn't be hard to modify it for drawArc()
/**
* Workaround for fillArc bug. Graphics.fillArc() works fine on android, but usually fails on iOS. There are three
* cases for its behavior.
*
* If width < height, it draws a circle with a diameter equal to width, and concentric with the intended ellipse.<br>
* If width > height, it draws nothing.<br>
* If width == height, it works correctly.
*
* To work around this we create a separate image, draw a circle, re-proportion it to the proper ellipse, then draw
* it to the Graphics object. It doesn't anti-alias very well.
*/
public static void fillArcWorkaround(Graphics masterG, int x, int y, int width, int height, int startAngle, int arcAngle) {
if (width == height) {
masterG.fillArc(x, y, width, height, startAngle, arcAngle);
} else {
int max = Math.max(width, height);
Image tempCircle = Image.createImage(max, max);
Graphics tempG = tempCircle.getGraphics();
tempG.setColor(masterG.getColor());
tempG.fillRect(0, 0, max, max);
// At this point tempCircle is just a colored rectangle. It becomes a circle when we apply the circle mask. The
// region outside the circle becomes transparent that way.
Image mask = Image.createImage(max, max);
tempG = mask.getGraphics();
tempG.setAntiAliased(masterG.isAntiAliased());
tempG.setColor(0);
tempG.fillRect(0, 0, max, max);
tempG.setColor(0xFF); // blue
tempG.fillArc(0, 0, max, max, startAngle, arcAngle);
tempCircle = tempCircle.applyMask(mask.createMask());
// Now tempCircle is a filled circle of the correct color. We now draw it in its intended proportions.
masterG.setAntiAliased(true);
masterG.drawImage(tempCircle, x, y, width, height);
}
}

Xamarin Pinch To Zoom and Pan Containers leaving their bounds

I have a label with a lot of text that I want to enable pinch-to-zoom and panning gesture recognizers in. I used the recipes from here and then nested them within each other.
https://developer.xamarin.com/guides/xamarin-forms/user-interface/gestures/pinch/
https://developer.xamarin.com/guides/xamarin-forms/user-interface/gestures/pan/
Problem is, both container objects allow you to move the label completely outside of it's normal bounds anywhere within the top level page view (demonstrated in the pictures below).
Any thoughts on how to implement some limits on these? I'm sure it's just placing some limits on the math in the container code, but I haven't found the right thing to change yet.
As you can see in these images, both the pinch-to-zoom container (without panning) and the pan container (without zooming) allow you to alter the control so it goes outside it's bounds.
Initial Layout:
Pinch-To-Zoom only
Panning only
Pinch and Pan
The links above have the container code, but here it is:
PinchToZoomContainer.cs
public class PinchToZoomContainer : ContentView
{
// Pinch Gesture variables
double currentScale = 1;
double startScale = 1;
double xOffset = 0;
double yOffset = 0;
public PinchToZoomContainer ()
{
var pinchGesture = new PinchGestureRecognizer ();
pinchGesture.PinchUpdated += OnPinchUpdated;
GestureRecognizers.Add (pinchGesture);
}
void OnPinchUpdated (object sender, PinchGestureUpdatedEventArgs e)
{
if (e.Status == GestureStatus.Started) {
// Store the current scale factor applied to the wrapped user interface element,
// and zero the components for the center point of the translate transform.
startScale = Content.Scale;
Content.AnchorX = 0;
Content.AnchorY = 0;
}
if (e.Status == GestureStatus.Running) {
// Calculate the scale factor to be applied.
currentScale += (e.Scale - 1) * startScale;
currentScale = Math.Max (1, currentScale);
// The ScaleOrigin is in relative coordinates to the wrapped user interface element,
// so get the X pixel coordinate.
double renderedX = Content.X + xOffset;
double deltaX = renderedX / Width;
double deltaWidth = Width / (Content.Width * startScale);
double originX = (e.ScaleOrigin.X - deltaX) * deltaWidth;
// The ScaleOrigin is in relative coordinates to the wrapped user interface element,
// so get the Y pixel coordinate.
double renderedY = Content.Y + yOffset;
double deltaY = renderedY / Height;
double deltaHeight = Height / (Content.Height * startScale);
double originY = (e.ScaleOrigin.Y - deltaY) * deltaHeight;
// Calculate the transformed element pixel coordinates.
double targetX = xOffset - (originX * Content.Width) * (currentScale - startScale);
double targetY = yOffset - (originY * Content.Height) * (currentScale - startScale);
// Apply translation based on the change in origin.
Content.TranslationX = targetX.Clamp (-Content.Width * (currentScale - 1), 0);
Content.TranslationY = targetY.Clamp (-Content.Height * (currentScale - 1), 0);
// Apply scale factor
Content.Scale = currentScale;
}
if (e.Status == GestureStatus.Completed) {
// Store the translation delta's of the wrapped user interface element.
xOffset = Content.TranslationX;
yOffset = Content.TranslationY;
}
}
PanContainer.cs
public class PanContainer : ContentView
{
double startX, startY;
double x, y;
public PanContainer ()
{
// Set PanGestureRecognizer.TouchPoints to control the
// number of touch points needed to pan
var panGesture = new PanGestureRecognizer ();
panGesture.PanUpdated += OnPanUpdated;
GestureRecognizers.Add (panGesture);
}
void OnPanUpdated (object sender, PanUpdatedEventArgs e)
{
switch (e.StatusType) {
case GestureStatus.Started:
startX = Content.TranslationX;
startY = Content.TranslationY;
break;
case GestureStatus.Running:
// Translate and ensure we don't pan beyond the wrapped user interface element bounds.
//Content.TranslationX = Math.Max (Math.Min (0, x + e.TotalX), -Math.Abs (Content.Width - App.ScreenWidth));// App.ScreenWidth));
//Content.TranslationY = Math.Max (Math.Min (0, y + e.TotalY), -Math.Abs (Content.Height - App.ScreenHeight)); //App.ScreenHeight));
Content.TranslationX = startX + e.TotalX;
Content.TranslationY = startY + e.TotalY;
break;
case GestureStatus.Completed:
// Store the translation applied during the pan
x = Content.TranslationX;
y = Content.TranslationY;
break;
}
}
}
I imagine, on the PanContainer, my issue is in these lines that I had to comment out:
//Content.TranslationX = Math.Max (Math.Min (0, x + e.TotalX), -Math.Abs (Content.Width - App.ScreenWidth));// App.ScreenWidth));
//Content.TranslationY = Math.Max (Math.Min (0, y + e.TotalY), -Math.Abs (Content.Height - App.ScreenHeight)); //App.ScreenHeight));
I changed these to a more simple version because I can't find App.ScreenWidth or .ScreenHeight properties.
The pinch container, however, is just as it was originally in the recipe and still goes outside the bounds.
There is an IsClippedToBounds property that helped me with this issue.
For example:
<PanContainer IsClippedToBounds="true">
<PanContainer.Content>
<Image x:Name="SomeImage" />
</PanContainer.Content>
</PanContainer>
To get pinch and pan, you can either wrap a pinch element in a pan element or vice versa, or you can create a single class with the functions from both the pinch and pan classes. The latter is probably better.
That alone will probably not work exactly as you expect though because the calculations in the pinch and pan functionality are not aware of each other, so if for example you pinch to zoom in then the pan functionality doesn't know that it can now pan further.
This answer is mostly likely very late for your needs, Chet... but, you can simply wrap the whole thing in a ScrollView (which you will appropriately locate and/or size to your needs). That should work as expected.
<ScrollView Grid.Column="2" VerticalOptions="Start">
<PanContainer>
<PanContainer.Content>
<Image x:Name="SomeImage" Aspect="AspectFit" />
</PanContainer.Content>
</PanContainer>
</ScrollView>
Cheers!
Mike

strange droppable() bug on iPad, works on iPhone

I've tried various web searches, but I can't seem to find anything that relates to my problem.
To quickly delineate the problem:
HTML5 Cordova iOS app (7.1 -> 8.1)
uses draggable elements
I only have issues on the iPad, not on the iPhone
The HTML5 app itself works flawlessly in a web-browser
The app itself is a biology app that teaches translation - decoding RNA into a amino acid sequence, i.e. a protein.
For this, the user sees the sequence and drags the correct amino acid onto it. The amino acid is a draggable element and the target div is a droppable. One amino acid at a time a chain is built. Please refer to the screenshot to get an idea (can't embed yet).
http://i.stack.imgur.com/S4UpF.png
In order to fit all screens, I "transform: scale" the app accordingly (fixed size is ~850x550). And to get rid of the associated jQuery bug with draggable (object movement would also change with the scaling factor), I've followed the instructions at http://gungfoo.wordpress.com/2013/02/15/jquery-ui-resizabledraggable-with-transform-scale-set/
// scaling to fit viewport
// sizing the page
var myPage = $('.page');
var pageWidth=myPage.width();
var pageHeight=myPage.height();
// sizing the iFrame
var myFrame = $('.container');
var frameWidth=myFrame.width();
var frameHeight=myFrame.height();
// scaleFactor horizontal
var horizontalScale=pageWidth/frameWidth;
// scaleFactor vertiacal
var verticalScale=pageHeight/frameHeight;
// global zoomScale variable
var zoomScale = 1; // default, required for draggable debug
// if page fits vertically - scale horizontally
if ((frameHeight * horizontalScale) <= pageHeight) {
myFrame.css({
'transform': 'scale('+horizontalScale+')',
'transform-origin': 'top',
});
// adding vertical margin, if possible
if (pageHeight > frameHeight*horizontalScale) {
var heightDifference = pageHeight - frameHeight*horizontalScale;
myPage.css({
'margin-top': heightDifference/2,
'height': pageHeight - heightDifference/2,
});
}
zoomScale = horizontalScale;
// else scale vertically
} else {
myFrame.css({
'transform': 'scale('+verticalScale+')',
'transform-origin': 'top',
});
zoomScale = verticalScale;
}
// draggable + scale transform fixes (http://gungfoo.wordpress.com/2013/02/15/jquery-ui-resizabledraggable-with-transform-scale-set/)
function startFix(event, ui) {
ui.position.left = 0;
ui.position.top = 0;
}
function dragFix(event, ui) {
var changeLeft = ui.position.left - ui.originalPosition.left; // find change in left
var newLeft = ui.originalPosition.left + changeLeft / zoomScale; // adjust new left by our zoomScale
var changeTop = ui.position.top - ui.originalPosition.top; // find change in top
var newTop = ui.originalPosition.top + changeTop / zoomScale; // adjust new top by our zoomScale
ui.position.left = newLeft;
ui.position.top = newTop;
}
I've already got a beta version on iTunes connect and it works great on an iPhone. On iPads, however, the droppable area is oddly small and shifted. That is, the div seems to be properly rendered - it is the box with the dashed border.
Has anyone else encountered a similar bug? I really have no idea how to fix it.
I have managed to solve the problem.
The issue was probably based on the viewport meta tag (0.5 scaling) interacting badly with the transform:scale resizing.
Simply removing all viewport meta arguments have solved the problem.

Resources