Scale of UIPinchGestureRecognizer in horizontal and vertical directions separately - ios

When using UIPinchGestureRecognizer what is the best way to detect/read the pinch scale in horizontal and vertical directions individually? I saw this post
UIPinchGestureRecognizer Scale view in different x and y directions
but I noticed there were so many going back and forth for such a seemingly routine task that I am not sure that is the best answer/way.
If not using UIPinchGestureRecognizer altogether for this purpose is the answer, what's the best way to detect pinch scale in two different directions?

Basically do this,
func _mode(_ sender: UIPinchGestureRecognizer)->String {
// very important:
if sender.numberOfTouches < 2 {
print("avoided an obscure crash!!")
return ""
}
let A = sender.location(ofTouch: 0, in: self.view)
let B = sender.location(ofTouch: 1, in: self.view)
let xD = fabs( A.x - B.x )
let yD = fabs( A.y - B.y )
if (xD == 0) { return "V" }
if (yD == 0) { return "H" }
let ratio = xD / yD
// print(ratio)
if (ratio > 2) { return "H" }
if (ratio < 0.5) { return "V" }
return "D"
}
That function will return H, V, D for you .. horizontal, vertical, diagonal.
You would use it something like this ...
func yourSelector(_ sender: UIPinchGestureRecognizer) {
// your usual code such as ..
// if sender.state == .ended { return } .. etc
let mode = _mode(sender)
print("the mode is \(mode) !!!")
// in this example, we only care about horizontal pinches...
if mode != "H" { return }
let vel = sender.velocity
if vel < 0 {
print("you're squeezing the screen!")
}
}

In my C# I do the following
private double _firstDistance = 0;
private int _firstScaling = 0;
private void PinchHandler(UIPinchGestureRecognizer pinchRecognizer)
{
nfloat x1, y1, x2, y2 = 0;
var t1 = pinchRecognizer.LocationOfTouch(0, _previewView);
x1 = t1.X;
y1 = t1.Y;
var t2 = pinchRecognizer.LocationOfTouch(1, _previewView);
x2 = t2.X;
y2 = t2.Y;
if (pinchRecognizer.State == UIGestureRecognizerState.Began)
{
_firstDistance = Math.Sqrt(Math.Pow((x2 - x1), 2) + Math.Pow((y2 - y1), 2));
_firstScaling = _task.TextTemplates[_selectedTextTemplate].FontScaling;
}
if (pinchRecognizer.State == UIGestureRecognizerState.Changed)
{
var distance = Math.Sqrt(Math.Pow((x2 - x1), 2) + Math.Pow((y2 - y1), 2));
var fontScaling = Convert.ToInt32((distance - _firstDistance) / _previewView.Frame.Height * 100);
fontScaling += _firstScaling;
_task.TextTemplates[_selectedTextTemplate].FontScaling = fontScaling;
UpdateBitmapPreview();
}
}
I calculate the distance between the two points when pinch "began" and hold that value in two privates. Then I calculate a scaling (fontScaling) based on the first measured distance and the second one (in "changed").
I use my own view (_previewView) to set as base (100%), but you can use View.Bounds.height instead or the width for that matter. in my case, I always have a square view, so height == width in my app.

Related

How to correctly drag the view around the circle?

I want the green dot to follow the touch point in a circular path, but it doesn't seem to be doing it right.
It seems like there is an unwanted offset somewhere but I can't find it on my own for quite some time.
Here is my code:
#Preview
#Composable
fun Test() {
val touchPoint = remember { mutableStateOf(Offset.Zero) }
Scaffold {
Column() {
Box(Modifier.height(100.dp).fillMaxWidth().background(Color.Blue))
Layout(
modifier = Modifier.aspectRatio(1f).fillMaxSize(),
content = {
Box(
Modifier
.size(48.dp)
.clip(CircleShape)
.background(Color.Green)
.pointerInput(Unit) {
detectDragGestures(
onDrag = { change, dragAmount ->
change.consumeAllChanges()
touchPoint.value += dragAmount
}
)
}
)
}
) { measurables, constraints ->
val dot = measurables.first().measure(constraints.copy(minHeight = 0, minWidth = 0))
val width = constraints.maxWidth
val height = constraints.maxHeight
val centerX = width / 2
val centerY = height / 2
val lengthFromCenter = width / 2 - dot.width / 2
val touchX = touchPoint.value.x
val touchY = touchPoint.value.y
layout(width, height) {
// I planned to achieve the desired behaviour with the following steps:
// 1. Convert cartesian coordinates to polar ones
val r = sqrt(touchX.pow(2) + touchY.pow(2))
val angle = atan2(touchY.toDouble(), touchX.toDouble())
// 2. Use fixed polar radius
val rFixed = lengthFromCenter
// 3. Convert it back to cartesian coordinates
val x = rFixed * cos(angle)
val y = rFixed * sin(angle)
// 4. Layout on screen
dot.place(
x = (x + centerX - dot.width / 2).roundToInt(),
y = (y + centerY - dot.height / 2).roundToInt()
)
}
}
Box(Modifier.fillMaxSize().background(Color.Blue))
}
}
}
I'm definitely missing something but don't know what exactly. What am I doing wrong?
touchPoint.value += dragAmount
Is in pixel values, and you're updating the position of the dot with pixel values, where it requires dp values. If you update that with
private fun Float.pxToDp(context: Context): Dp = // Float or Int, depends on the value you have, or Double
(this / context.resources.displayMetrics.density).dp
The amount with which it will be moved, will be smaller and reflect the dragging made by the user
You can easily achieve this by using some math:
#Composable
fun CircularView(
content: #Composable () -> Unit
) {
var middle by remember {
mutableStateOf(Offset.Zero)
}
var size by remember {
mutableStateOf(0.dp)
}
var dragAngle by remember {
mutableStateOf(0f)
}
Canvas(modifier = Modifier.size(size)) {
drawCircle(
color = Color.Red,
center = middle,
style = Stroke(1.dp.toPx())
)
}
Layout(
content = content,
modifier = Modifier.pointerInput(true) {
detectDragGestures(
onDrag = { change, _ ->
change.consumeAllChanges()
val positionOfDrag = change.position
val previousPosition = change.previousPosition
dragAngle += atan2(
positionOfDrag.x - middle.x,
positionOfDrag.y - middle.y
) - atan2(
previousPosition.x - middle.x,
previousPosition.y - middle.y
)
}
)
}
) { measurables, constraints ->
val placeables = measurables.map { it.measure(constraints) }
val layoutWidth = constraints.maxWidth
val layoutHeight = constraints.maxHeight
layout(layoutWidth, layoutHeight) {
val childCount = placeables.size
if (childCount == 0) return#layout
val middleX = layoutWidth / 2f
val middleY = layoutHeight / 2f
middle = Offset(middleX, middleY)
val angleBetween = 2 * PI / childCount
val radius =
min(
layoutWidth - (placeables.maxByOrNull { it.width }?.width ?: 0),
layoutHeight - (placeables.maxByOrNull { it.height }?.height ?: 0)
) / 2
size = (radius * 2).toDp()
placeables.forEachIndexed { index, placeable ->
val angle = index * angleBetween - PI / 2 - dragAngle
val x = middleX + (radius) * cos(angle) - placeable.width / 2f
val y = middleY + (radius) * sin(angle) - placeable.height / 2f
placeable.placeRelative(x = x.toInt(), y = y.toInt())
}
}
}
}
On the calling side:
CircularView {
repeat(10) {
Box(
modifier = Modifier
.background(
Color(
red = random.nextInt(255),
green = random.nextInt(255),
blue = random.nextInt(255)
), shape = CircleShape
)
.size(50.dp),
contentAlignment = Alignment.Center
) {
Text(text = it.toString(), fontSize = 12.sp, color = Color.White)
}
}
}

XamForms.Controls.Calendar Special Dates Text Blurry

I'm using XamForms.Controls.Calendar in my Xamarin.Forms project everything works great, but the texts entered in the "Text" section of the SpeacialDates property appear blurred on the iOS platform, how can I solve it?
DrawText feature ios also have the same way, despite the intense research of this feature could not solve the blur problem
https://imgur.com/a/E0UZnD9 "Screenshot"
protected void DrawText(CGContext g, Pattern p, CGRect r)
{
if (string.IsNullOrEmpty(p.Text)) return;//Copperplate-Light" KohinoorTelugu-Light
string fontname = Device.Idiom == TargetIdiom.Tablet ? "KohinoorTelugu-Light" : "GillSans-UltraBold";//GillSans-UltraBold
var bounds = p.Text.StringSize(UIFont.FromName(fontname, p.TextSize));
//"GillSans-UltraBold
var al = (int)p.TextAlign;
var x = r.X;
if ((al & 2) == 2) // center
{
x = r.X + (int)Math.Round(r.Width / 2.0) - (int)Math.Round(bounds.Width / 2.0);
}
else if ((al & 4) == 4) // right
{
x = (r.X + r.Width) - bounds.Width - 2;
}
var y = r.Y + (int)Math.Round(bounds.Height / 2.0) + 2;
if ((al & 16) == 16) // middle
{
y = r.Y + (int)Math.Ceiling(r.Height / 2.0) + (int)Math.Round(bounds.Height / 5.0);
}
else if ((al & 32) == 32) // bottom
{
y = (r.Y + r.Height) - 2;
}
g.SaveState();
g.SetShouldAntialias(true);
g.SetShouldSmoothFonts(true);
g.SetAllowsFontSmoothing(true);
g.SetAllowsAntialiasing(true);
g.SetShouldSubpixelPositionFonts(false);
g.InterpolationQuality = CGInterpolationQuality.High;
g.SetAllowsSubpixelPositioning(false);
g.SetAllowsFontSubpixelQuantization(false);
g.InterpolationQuality = CGInterpolationQuality.High;
g.TranslateCTM(0, Bounds.Height);
g.ScaleCTM(1, -1);
g.SetStrokeColor(p.TextColor.ToCGColor());
g.DrawPath(CGPathDrawingMode.EOFillStroke);
g.SetFillColor(p.TextColor.ToCGColor());
g.SetTextDrawingMode(CGTextDrawingMode.FillStrokeClip);
g.SelectFont(fontname, p.TextSize, CGTextEncoding.MacRoman);
g.ShowTextAtPoint(x, Bounds.Height - y, p.Text);
g.RestoreState();
}
}

Get area of intersecting line (CGPoints)

I have an Array of CGPoints and I would like to find those points, which build a shape. Please see the attached image:
The red circles just mark the points I have.
How can the area with the question mark be found?
Thanks.
You are going to have to start with your first line segment and check for intersections. Obviously if the first two line segments intersect then they are the same line and your shape is just a line, so ignore that case. As you continue down your line segments once you find a segment pair that intersect then you have your shape.
Check line segment 2 against line segment 1. Then check line segment 3 against line segment 2, then against line segment 1. Then check 4 against 3, then 2, then 1, etc... If you find that line segment 7 intersects with line segment 3, delete the first point of line segment 3 and se it to the intersection point you found. Then delete the last point of line segment 7 and set it to the intersection point you found. There you have your shape.
Here is an example method to find the intersection of 2 line segments (written in C#, but it's straight math so it should be very easy to convert to any language you would like). Taken from here:
// Determines if the lines AB and CD intersect.
static bool LinesIntersect(PointF A, PointF B, PointF C, PointF D)
{
PointF CmP = new PointF(C.X - A.X, C.Y - A.Y);
PointF r = new PointF(B.X - A.X, B.Y - A.Y);
PointF s = new PointF(D.X - C.X, D.Y - C.Y);
float CmPxr = CmP.X * r.Y - CmP.Y * r.X;
float CmPxs = CmP.X * s.Y - CmP.Y * s.X;
float rxs = r.X * s.Y - r.Y * s.X;
if (CmPxr == 0f)
{
// Lines are collinear, and so intersect if they have any overlap
return ((C.X - A.X < 0f) != (C.X - B.X < 0f))
|| ((C.Y - A.Y < 0f) != (C.Y - B.Y < 0f));
}
if (rxs == 0f)
return false; // Lines are parallel.
float rxsr = 1f / rxs;
float t = CmPxs * rxsr;
float u = CmPxr * rxsr;
return (t >= 0f) && (t <= 1f) && (u >= 0f) && (u <= 1f);
}
I've figured out the solution.
This function returns a polygon for each area that gets closed by intersecting lines.
func intersectionOfLineFrom(p1: CGPoint, to p2: CGPoint, withLineFrom p3: CGPoint, to p4: CGPoint) -> NSValue? {
let d: CGFloat = (p2.x - p1.x) * (p4.y - p3.y) - (p2.y - p1.y) * (p4.x - p3.x)
if d == 0 {
return nil
}
// parallel lines
let u: CGFloat = ((p3.x - p1.x) * (p4.y - p3.y) - (p3.y - p1.y) * (p4.x - p3.x)) / d
let v: CGFloat = ((p3.x - p1.x) * (p2.y - p1.y) - (p3.y - p1.y) * (p2.x - p1.x)) / d
if u < 0.0 || u > 1.0 {
return nil
}
// intersection point not between p1 and p2
if v < 0.0 || v > 1.0 {
return nil
}
// intersection point not between p3 and p4
var intersection: CGPoint = CGPointZero
intersection.x = p1.x + u * (p2.x - p1.x)
intersection.y = p1.y + u * (p2.y - p1.y)
return NSValue(CGPoint: intersection)
}
func intersectedPolygons(points: [CGPoint]) -> [[CGPoint]] {
var removeIndexBelow : Int = 0
var removeIndexAbove : Int = 0
var resultArrays : [[CGPoint]] = [[CGPoint]]()
for i in 1..<points.count {
let firstLineStart = points[i-1] as CGPoint
let firstLineEnd = points[i] as CGPoint
for var j = points.count-1; j > i+1; j-- {
let lastLineStart = points[j-1] as CGPoint
let lastLineEnd = points[j] as CGPoint
if let intersect: NSValue = self.intersectionOfLineFrom(firstLineStart, to: firstLineEnd, withLineFrom: lastLineStart, to: lastLineEnd){
var pointsCopy = points
let intersection = intersect.CGPointValue()
pointsCopy[i-1] = intersection
pointsCopy[j] = intersection
removeIndexBelow = i
removeIndexAbove = j
let fullPoly = Array(pointsCopy[removeIndexBelow-1..<removeIndexAbove])
resultArrays.append(fullPoly)
break;
}
}
}
return resultArrays
}

iOS drag and drop within parent bounds

I'm try to implement drag and drop for some UIImageViews within a UIView. It is currently working, with the exception that you can drag the image views out of the parent and off the screen. I know I have to check to see if the views go outside the parent bounds.. but im struggling to achieve it! Does anyone have any experience with this? For the Windows Phone client, all that is needed is the following line;
var dragBehavior = new MouseDragElementBehavior {ConstrainToParentBounds = true};
The current gesture code I have is here;
private UIPanGestureRecognizer CreateGesture(UIImageView imageView)
{
float dx = 0;
float dy = 0;
var panGesture = new UIPanGestureRecognizer((pg) =>
{
if ((pg.State == UIGestureRecognizerState.Began || pg.State == UIGestureRecognizerState.Changed) && (pg.NumberOfTouches == 1))
{
var p0 = pg.LocationInView(View);
if (dx == 0)
dx = (float)p0.X - (float)imageView.Center.X;
if (dy == 0)
dy = (float)p0.Y - (float)imageView.Center.Y;
float newX = (float)p0.X - dx;
float newY = (float)p0.Y - dy;
var p1 = new PointF(newX, newY);
imageView.Center = p1;
}
else if (pg.State == UIGestureRecognizerState.Ended)
{
dx = 0;
dy = 0;
}
});
return panGesture;
}
I resolved this with the following code;
var panGesture = new UIPanGestureRecognizer((pg) =>
{
if ((pg.State == UIGestureRecognizerState.Began || pg.State == UIGestureRecognizerState.Changed) && (pg.NumberOfTouches == 1))
{
var p0 = pg.LocationInView(View);
if (dx == 0)
dx = (float)p0.X - (float)imageView.Center.X;
if (dy == 0)
dy = (float)p0.Y - (float)imageView.Center.Y;
float newX = (float)p0.X - dx;
float newY = (float)p0.Y - dy;
var p1 = new PointF(newX, newY);
// If too far right...
if (p1.X > imageView.Superview.Bounds.Size.Width - (imageView.Bounds.Size.Width / 2))
p1.X = (float) imageView.Superview.Bounds.Size.Width - (float)imageView.Bounds.Size.Width / 2;
else if (p1.X < 0 + (imageView.Bounds.Size.Width / 2)) // If too far left...
p1.X = 0 + (float)(imageView.Bounds.Size.Width / 2);
// If too far down...
if (p1.Y > imageView.Superview.Bounds.Size.Height - (imageView.Bounds.Size.Height / 2))
p1.Y = (float)imageView.Superview.Bounds.Size.Height - (float)imageView.Bounds.Size.Height / 2;
else if (p1.Y < 0 + (imageView.Bounds.Size.Height / 2)) // If too far up...
p1.Y = 0 + (float)(imageView.Bounds.Size.Height / 2);
imageView.Center = p1;
}
else if (pg.State == UIGestureRecognizerState.Ended)
{
// reset offsets when dragging ends so that they will be recalculated for next touch and drag that occurs
dx = 0;
dy = 0;
}
});

Find the precise centroid (as an MKMapPoint) of an MKPolygon

This means excluding the area(s) of any interiorPolygons.
Once one has the centroid of the outer points polygon, how does one (i.e., in the form of an Objective-C example) adjust the centroid by the subtractive interiorPolygons? Or is there a more elegant way to compute the centroid in one go?
If you help get the code working, it will be open sourced (WIP here).
Might be helpful:
http://www.ecourses.ou.edu/cgi-bin/eBook.cgi?topic=st&chap_sec=07.2&page=case_sol
https://en.wikipedia.org/wiki/Centroid#Centroid_of_polygon
Thinking about it today, it makes qualitative sense that adding each interior centroid weighted by area to the exterior centroid would arrive at something sensible. (A square with an interior polygon (hole) on the left side would displace the centroid right, directly proportional to the area of the hole.)
Not to scale:
- (MKMapPoint)calculateCentroid
{
switch (self.pointCount) {
case 0: return MKMapPointMake(0.0,
0.0);
case 1: return MKMapPointMake(self.points[0].x,
self.points[0].y);
case 2: return MKMapPointMake((self.points[0].x + self.points[1].x) / 2.0,
(self.points[0].y + self.points[1].y) / 2.0);
}
// onward implies pointCount >= 3
MKMapPoint centroid;
MKMapPoint *previousPoint = &(self.points[self.pointCount-1]); // for i=0, wrap around to the last point
for (NSUInteger i = 0; i < self.pointCount; ++i) {
MKMapPoint *point = &(self.points[i]);
double delta = (previousPoint->x * point->y) - (point->x * previousPoint->y); // x[i-1]*y[i] + x[i]*y[i-1]
centroid.x += (previousPoint->x + point->x) * delta; // (x[i-1] + x[i]) / delta
centroid.y += (previousPoint->y + point->y) * delta; // (y[i-1] + y[i]) / delta
previousPoint = point;
}
centroid.x /= 6.0 * self.area;
centroid.y /= 6.0 * self.area;
// interiorPolygons are holes (subtractive geometry model)
for (MKPolygon *interiorPoly in self.interiorPolygons) {
if (interiorPoly.area == 0.0) {
continue; // avoid div-by-zero
}
centroid.x += interiorPoly.centroid.x / interiorPoly.area;
centroid.y += interiorPoly.centroid.y / interiorPoly.area;
}
return centroid;
}
in Swift 5
private func centroidForCoordinates(_ coords: [CLLocationCoordinate2D]) -> CLLocationCoordinate2D? {
guard let firstCoordinate = coordinates.first else {
return nil
}
guard coords.count > 1 else {
return firstCoordinate
}
var minX = firstCoordinate.longitude
var maxX = firstCoordinate.longitude
var minY = firstCoordinate.latitude
var maxY = firstCoordinate.latitude
for i in 1..<coords.count {
let current = coords[i]
if minX > current.longitude {
minX = current.longitude
} else if maxX < current.longitude {
maxX = current.longitude
} else if minY > current.latitude {
minY = current.latitude
} else if maxY < current.latitude {
maxY = current.latitude
}
}
let centerX = minX + ((maxX - minX) / 2)
let centerY = minY + ((maxY - minY) / 2)
return CLLocationCoordinate2D(latitude: centerY, longitude: centerX)
}

Resources