UILabel blurred text using UIPinchGestureRecognizer - ios

I have a UILabel; when I enlarge it using UIPinchGestureRecognizer, the text becomes blurred.
I use CGAffineTransformScale my code
self.myLabel.transform = CGAffineTransformScale(self.myLabel.transform, pinchRecognizer.scale, pinchRecognizer.scale);
How to fix it?

I found how to solve this problem, it was so easy.
give scale:
CGFloat scale = self.myLable.transform.a *pinchRecognizer.scale *[UIScreen mainScreen].scale;
this a - (self.myLable.transform.a) return current scale factor.
self.myLable.transform = CGAffineTransformScale(self.myLable.transform, pinchRecognizer.scale, pinchRecognizer.scale);
[self.myLable setContentScaleFactor:scale];
Done,
happy coding!

After a lot of trying and failed. I found a solution.
#IBAction func handlePinch(recognizer : UIPinchGestureRecognizer) {
var pinchScale = recognizer.scale
signatureLabel.layer.contentsScale = UIScreen.main.scale + pinchScale;
signatureLabel.transform = signatureLabel.transform.scaledBy(x: pinchScale, y:pinchScale)
pinchScale = round(pinchScale * 1000) / 1000.0
if recognizer.state == .changed {
signatureLabel.font = UIFont(name: signatureLabel.font.fontName, size: signatureLabel.font.pointSize * pinchScale)
pinchScale = recognizer.scale
}
recognizer.scale = 1
}
Although, It is not working properly if you don't update the constraints for that UILabel. So, in the storyboard I have added the Vertical & Horizontal center constraints to my label. Then, created the outlets into my class. And in my handlePan method I have done:
#IBAction func handlePan(recognizer:UIPanGestureRecognizer) {
let translation = recognizer.translation(in: recognizer.view)
self.signatureLabel.center = CGPoint(x:self.signatureLabel.center.x + translation.x,
y:self.signatureLabel.center.y + translation.y)
signatureLabelCenterConstraint.constant = signatureLabelCenterConstraint.constant + translation.y
signatureLabelCenterXConstraint.constant = signatureLabelCenterXConstraint.constant + translation.x
signatureLabel.setNeedsLayout()
recognizer.setTranslation(.zero, in: recognizer.view)
}
I hope that help everyone!

Related

How to Increase/Decrease font size with Pan Gesture?

I am applying pan gesture on UILabel. I use one finger scale to increase and decrease the size of the UILabel.
I tried using scale to add and subtract value from font size, but I am not getting the exact result.
#objc
func handleRotateGesture(_ recognizer: UIPanGestureRecognizer) {
let touchLocation = recognizer.location(in: self.superview)
let center = self.center
switch recognizer.state {
case .began:
self.deltaAngle = CGFloat(atan2f(Float(touchLocation.y - center.y), Float(touchLocation.x - center.x))) - CGAffineTransformGetAngle(self.transform)
self.initialBounds = self.bounds
self.initialDistance = CGPointGetDistance(point1: center, point2: touchLocation)
case .changed:
let angle = atan2f(Float(touchLocation.y - center.y), Float(touchLocation.x - center.x))
let angleDiff = Float(self.deltaAngle) - angle
self.transform = CGAffineTransform(rotationAngle: CGFloat(-angleDiff))
if let label = self.contentView as? UILabel {
var scale = CGPointGetDistance(point1: center, point2: touchLocation) / self.initialDistance
let minimumScale = CGFloat(self.minimumSize) / min(self.initialBounds.size.width, self.initialBounds.size.height)
scale = max(scale, minimumScale)
let scaledBounds = CGRectScale(self.initialBounds, wScale: scale, hScale: scale)
var pinchScale = scale
pinchScale = round(pinchScale * 1000) / 1000.0
var fontSize = label.font.pointSize
if(scale > minimumScale){
if (self.bounds.height > scaledBounds.height) {
// fontSize = fontSize - pinchScale
label.font = UIFont(name: label.font.fontName, size: fontSize - pinchScale)
}
else{
// fontSize = fontSize + pinchScale
label.font = UIFont(name: label.font.fontName, size: fontSize + pinchScale)
}
} else {
label.font = UIFont( name: label.font.fontName, size: fontSize)
}
print("PinchScale -- \(pinchScale), FontSize = \(fontSize)")
self.bounds = scaledBounds
} else {
var scale = CGPointGetDistance(point1: center, point2: touchLocation) / self.initialDistance
let minimumScale = CGFloat(self.minimumSize) / min(self.initialBounds.size.width, self.initialBounds.size.height)
scale = max(scale, minimumScale)
let scaledBounds = CGRectScale(self.initialBounds, wScale: scale, hScale: scale)
self.bounds = scaledBounds
}
self.setNeedsDisplay()
default:
break
}
}
However, we can achieve this using UIPinchGestureRecognizer. But how can we do the same effect with UIPanGestureRecognizer? Any help would be appreciated. Thanks.
Right now you’re adding and removing points to the label’s current font size. I’d suggest a simpler pattern, more consistent with what you’re doing elsewhere, namely just capturing the the initial point size at the start of the gesture and then, as the user’s finger moves, apply a scale to that saved value. So, I’d suggest:
Define property to save the initial font size:
var initialPointSize: CGFloat = 0
In the .began of the gesture, capture the current size
initialPointSize = (contentView as? UILabel)?.font?.pointSize ?? 0
In the .changed, adjust the font size:
let pinchScale = (scale * 1000).rounded() / 1000
label.font = label.font.withSize(initialPointSize * pinchScale)
As an aside, I’m not sure that it’s necessary to round the scale to three decimal places, but you had that in your original code snippet, so I preserved that.
Personally, I’d follow the same basic pattern with the transform:
Define a properties to capture the starting angle and the view’s current transform:
var initialAngle: CGFloat = 0
var initialTransform: CGAffineTransform = .identity
In the .began, capture the current the starting angle and existing transform:
initialAngle = atan2(touchLocation.y - center.y, touchLocation.x - center.x)
initialTransform = transform
In the .changed, update the transform:
let angle = atan2(touchLocation.y - center.y, touchLocation.x - center.x)
transform = initialTransform.rotated(by: angle - initialAngle)
This saves you from reverse engineering the angle associated with the current transform with CGAffineTransformGetAngle and instead just apply rotated(by:) to the saved transform.
So this, like the point size and the bounds, represents a consistent pattern: Capture the starting value in .began and just apply whatever change you need in .changed.
A few unrelated observations:
All those self. references are not needed. It just adds noise that makes it harder to read the code.
All of those casts between CGFloat and Float are not needed. If you use the atof2 function instead of atof2f, they all just work with CGFloat without any casting. E.g., instead of
let angle = atan2f(Float(touchLocation.y - center.y), Float(touchLocation.x - center.x))
let angleDiff = Float(self.deltaAngle) - angle
self.transform = CGAffineTransform(rotationAngle: CGFloat(-angleDiff))
You can just do:
let angle = atan2(touchLocation.y - center.y, touchLocation.x - center.x)
transform = CGAffineTransform(rotationAngle: angle - deltaAngle)
You actually don’t need any of those casts that are currently sprinkled throughout the code snippet.
All of those bounds.size.width and bounds.size.height can just be bounds.width and bounds.height respectively, again removing noise from the code.
When adjusting the font size, rather than:
label.font = UIFont(name: label.font.fontName, size: fontSize)
You should just use:
label.font = label.font.withSize(fontSize)
That way you preserve all of the essential characteristics of the font (the weight, etc.), and just adjust the size.
In your if let label = contentView as? UILabel test, the same five lines of code in your else clause appear in the if clause, too. You should just move those common lines before the if-else statement, and then you can lose the else clause entirely, simplifying your code.

Move a circular UIView inside another circular UIView

I'm trying to do a joystick swift, and I'm almost there.
But I have a problem, the movement of the joystick is smooth when I move it "in the middle", but when the joystick touches the edges of "its container" it becomes laggy.
But I know why, it's because I allow the joystick to move only if it doesn't touch the edges, and I don't know how to correct this problem (what code to put in the else).
Here's my code and a GIF so you can see better.
import UIKit
import SnapKit
class ViewController: UIViewController {
let joystickSize = 150
let substractSize = 200
let joystickOffset = 10
let joystickSubstractView = UIView()
let joystickView = UIView()
override func viewDidLoad() {
super.viewDidLoad()
joystickSubstractView.backgroundColor = .gray
joystickSubstractView.layer.cornerRadius = CGFloat(substractSize / 2)
self.view.addSubview(joystickSubstractView)
let panGesture = UIPanGestureRecognizer(target: self, action: #selector(dragJoystick))
joystickView.isUserInteractionEnabled = true
joystickView.addGestureRecognizer(panGesture)
joystickView.backgroundColor = .white
joystickView.layer.cornerRadius = CGFloat(joystickSize / 2)
joystickSubstractView.addSubview(joystickView)
joystickSubstractView.snp.makeConstraints {
$0.width.height.equalTo(substractSize)
$0.centerX.equalToSuperview()
$0.bottom.equalToSuperview().inset(150)
}
joystickView.snp.makeConstraints {
$0.width.height.equalTo(joystickSize)
$0.center.equalToSuperview()
}
}
#objc func dragJoystick(_ sender: UIPanGestureRecognizer) {
self.view.bringSubviewToFront(joystickView)
let translation = sender.translation(in: self.view)
let joystickCenter = joystickView.convert(joystickView.center, to: self.view)
let futureJoystickCenter = CGPoint(x: joystickCenter.x - joystickView.frame.minX + translation.x,
y: joystickCenter.y - joystickView.frame.minY + translation.y)
let distanceBetweenCenters = hypot(futureJoystickCenter.x - joystickSubstractView.center.x,
futureJoystickCenter.y - joystickSubstractView.center.y)
if CGFloat(substractSize / 2 + joystickOffset) >= (distanceBetweenCenters + CGFloat(joystickSize / 2)) {
joystickView.center = CGPoint(x: joystickView.center.x + translation.x,
y: joystickView.center.y + translation.y)
} else {
// I don't know what to put here to make the joystick "smoother"
}
sender.setTranslation(CGPoint.zero, in: self.view)
}
}
Thank you for your help
Here is one approach...
calculate the maximum available distance from the center of the outer circle to the center of the inner circle, as a radius
track the touch / pan gesture's location relative to the center of the outer circle
if the new distance from the center of the inner circle (the touch point) to the center of the outer circle is greater than the max radius, move the inner circle center to the intersection of the touch-to-center line and the edge of the radius circle
Here's how it would look, with the center of the "joystick" view identified with a green dot, and the radius circle shown as a red outline:
You can give it a try with this code:
class JoyStickViewController: UIViewController {
let joystickSize: CGFloat = 150
let substractSize: CGFloat = 200
var innerRadius: CGFloat = 0.0
let joystickSubstractView = UIView()
let joystickView = UIView()
override func viewDidLoad() {
super.viewDidLoad()
joystickSubstractView.backgroundColor = .gray
joystickSubstractView.layer.cornerRadius = CGFloat(substractSize / 2)
self.view.addSubview(joystickSubstractView)
let panGesture = UIPanGestureRecognizer(target: self, action: #selector(dragJoystick(_:)))
joystickView.isUserInteractionEnabled = true
joystickView.addGestureRecognizer(panGesture)
joystickView.backgroundColor = .yellow
joystickView.layer.cornerRadius = CGFloat(joystickSize / 2)
joystickSubstractView.addSubview(joystickView)
joystickSubstractView.snp.makeConstraints {
$0.width.height.equalTo(substractSize)
$0.centerX.equalToSuperview()
$0.bottom.equalToSuperview().inset(150)
}
joystickView.snp.makeConstraints {
$0.width.height.equalTo(joystickSize)
$0.center.equalToSuperview()
}
// if you want the "joystick" circle to overlap the "outer circle" a bit, adjust this value
innerRadius = (substractSize - joystickSize) * 0.5
// start debugging / clarification...
// add a center "dot" to the joystick view
// add a red circle showing the inner radius - where we want to restrict the center of the joystick view
let jsCenterView = UIView()
jsCenterView.backgroundColor = .green
jsCenterView.layer.cornerRadius = 2.0
joystickView.addSubview(jsCenterView)
jsCenterView.snp.makeConstraints {
$0.width.height.equalTo(4.0)
$0.center.equalToSuperview()
}
let v = UIView()
v.backgroundColor = .clear
v.layer.borderColor = UIColor.red.cgColor
v.layer.borderWidth = 2
v.layer.cornerRadius = innerRadius
v.isUserInteractionEnabled = false
joystickSubstractView.addSubview(v)
v.snp.makeConstraints {
$0.width.height.equalTo(innerRadius * 2.0)
$0.center.equalToSuperview()
}
// end debugging / clarification
}
func lineLength(from pt1: CGPoint, to pt2: CGPoint) -> CGFloat {
return hypot(pt2.x - pt1.x, pt2.y - pt1.y)
}
func pointOnLine(from startPt: CGPoint, to endPt: CGPoint, distance: CGFloat) -> CGPoint {
let totalDistance = lineLength(from: startPt, to: endPt)
let totalDelta = CGPoint(x: endPt.x - startPt.x, y: endPt.y - startPt.y)
let pct = distance / totalDistance;
let delta = CGPoint(x: totalDelta.x * pct, y: totalDelta.y * pct)
return CGPoint(x: startPt.x + delta.x, y: startPt.y + delta.y)
}
#objc func dragJoystick(_ sender: UIPanGestureRecognizer) {
let touchLocation = sender.location(in: joystickSubstractView)
let outerCircleViewCenter = CGPoint(x: joystickSubstractView.bounds.width * 0.5, y: joystickSubstractView.bounds.height * 0.5)
var newCenter = touchLocation
let distance = lineLength(from: touchLocation, to: outerCircleViewCenter)
// if the touch would put the "joystick circle" outside the "outer circle"
// find the point on the line from center to touch, at innerRadius distance
if distance > innerRadius {
newCenter = pointOnLine(from: outerCircleViewCenter, to: touchLocation, distance: innerRadius)
}
joystickView.center = newCenter
}
}
Note: you can delete (or comment-out) the lines of code in viewDidLoad() between the // start debugging and // end debugging comments to remove the green center-dot and the red circle.

Limit UIPanGestureRecognizer to boundaries swift

I have a images inside uicollectionview cell which scroll horizontally, I want to achieve a feature like the facebook and photo app apple, you click on image and it covers the whole screen. You can pinch and pan the image, I want to add certain limitations same like the facebook and photo app, like when you pinch the picture you can pan maximum to its width.
I want the image to recenter again if user try to move image out of the boundaries. I am adding some screenshots to give idea about it.
Right now I am using the simple code.
guard gestureRecognizer.view != nil else {return}
print(self.imgView.frame)
if self.imgView.frame.size.width < (self.imgOrignal.width+100) {
return
}
let piece = gestureRecognizer.view!
// Get the changes in the X and Y directions relative to
// the superview's coordinate space.
let translation = gestureRecognizer.translation(in: piece.superview)
if gestureRecognizer.state == .began {
self.initialCenter = piece.center
}
// Update the position for the .began, .changed, and .ended states
if gestureRecognizer.state != .cancelled {
// Add the X and Y translation to the view's original position.
let newCenter = CGPoint(x: initialCenter.x + translation.x, y: initialCenter.y + translation.y)
piece.center = newCenter
}
else {
// On cancellation, return the piece to its original location.
piece.center = initialCenter
}
}
I have resolved this issue by using UIScrollView zoom in and out, instead of using pinch and pan gesture, If any one wants to implement that functionality i am adding my code below.
func setZoomScale() {
let widthScale = self.bgView.frame.size.width / self.imgView.bounds.width
let heightScale = self.bgView.frame.size.height / self.imgView.bounds.height
let minScale = min(widthScale, heightScale)
self.imgScrollView.minimumZoomScale = minScale
self.imgScrollView.zoomScale = minScale
}
extension YOUR CLASS: UIScrollViewDelegate {
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return self.imgView
}
func scrollViewDidZoom(_ scrollView: UIScrollView) {
let imageViewSize = self.imgView.frame.size
let scrollViewSize = scrollView.bounds.size
let verticalInset = imageViewSize.height < scrollViewSize.height ? (scrollViewSize.height - imageViewSize.height) / 2 : 0
let horizontalInset = imageViewSize.width < scrollViewSize.width ? (scrollViewSize.width - imageViewSize.width) / 2 : 0
scrollView.contentInset = UIEdgeInsets(top: verticalInset, left: horizontalInset, bottom: verticalInset, right: horizontalInset)
}
}

Stop UIDynamicAnimator Whit view Limits

I have been trying to create a custom UIClass that implements an horizontal scrolling for its inner content by using UIPanGestureRecognizer.
My first success approach was:
#IBAction func dragAction(_ sender: UIPanGestureRecognizer) {
let velocity = sender.velocity(in: sender.view)
if velocity.x > 0{
//print("\(logClassName) Dragging Right ...")
if innerView!.frame.origin.x < CGFloat(0){
offSetTotal += 1
innerView?.transform = CGAffineTransform(translationX: CGFloat(offSetTotal), y: 0)
}
}
else{
//print("\(logClassName) Dragging Left ...")
if totalWidth - innerView!.bounds.maxX < innerView!.frame.origin.x{
offSetTotal -= 1
innerView?.transform = CGAffineTransform(translationX: CGFloat(offSetTotal), y: 0)
}
}
}
That way, althought is a little bit clunky, I am able to scroll from left to right (and reverse) and the innerview is always covering in the holderView.
Then, and because i wanted to get the scrolling effect that you would get with a UIScrolling view i tried the following approach
#objc func handleTap(_ pan: UIPanGestureRecognizer){
let translation = pan.translation(in: innerView)
let recogView = pan.view
let curX = recogView?.frame.origin.x
if pan.state == UIGestureRecognizerState.began {
self.animator.removeAllBehaviors()
recogView?.frame.origin.x = curX! + translation.x
pan.setTranslation(CGPoint.init(x: 0, y: 0), in: recogView)
}else if pan.state == UIGestureRecognizerState.changed {
recogView?.frame.origin.x = curX! + translation.x
pan.setTranslation(CGPoint.init(x: 0, y: 0), in: recogView)
}else if pan.state == UIGestureRecognizerState.ended {
let itemBehavior = UIDynamicItemBehavior(items: [innerView!]);
var velocity = pan.velocity(in: self)
print(velocity.y)
velocity.y = 0
itemBehavior.addLinearVelocity(velocity, for: innerView!);
itemBehavior.friction = 1
itemBehavior.resistance = 1;
//itemBehavior.elasticity = 0.8;
self.collision = UICollisionBehavior(items: [innerView!]);
self.collision!.collisionMode = .boundaries
self.animator.addBehavior(collision!)
self.animator.addBehavior(itemBehavior);
}
Now the problem i face is that it moves horizontally but it goes away and gets lose.
Is that the right approach? Is there a delegate?
The question was posted due to a lack of knowledge of UIScrollViews. I Knew how to do with Storyboard and only scroll vertically.
I ended up creating a programatically UIScrollView and setting up its content size with the inner view.
scrollView = UIScrollView(frame: bounds)
scrollView?.backgroundColor = UIColor.yellow
scrollView?.contentSize = innerView!.bounds.size
scrollView?.addSubview(innerView!)
addSubview(scrollView!)
But first I have defined the innerView.

Scaling image coordinates in UIImageView

So I have a project where I take an image, display it and depending on where you tap it gives back the rgb values.
However, the display on the iphone is much smaller than the image resolution so the image gets scaled down.
I tried to circumnavigate this by multiplying the coordinates of the tapLocation on the UIImageView by: image.x/imageview.x and image.y/imageview.y respectively.
But still the colors are way off.
My code:
#IBAction func imageTap(_ sender: UITapGestureRecognizer) {
if sender.state == .ended {
let location = sender.location(in: imageDisplay)
let widthFactor = image.size.width / imageDisplay.frame.width
let heightFactor = image.size.height / imageDisplay.frame.height
let scaledWidth = location.x * widthFactor
let scaledHeight = location.y * heightFactor
let scaledLocation = CGPoint(x: scaledWidth, y: scaledHeight)
let colorAtLocation = image.getPixelColor(pos: scaledLocation)
let rgbValues = colorAtLocation.rgb()
let rValue = rgbValues!.red
let gValue = rgbValues!.green
let bValue = rgbValues!.blue
redValue.text = "\(String(describing: rValue))"
greenValue.text = "\(String(describing: gValue))"
blueValue.text = "\(String(describing: bValue))"
colorViewer.backgroundColor = colorAtLocation
}
}
How should I calculate coordinate correctly?
Possible places where this could go wrong:
The 0;0 isn't where I think it is
The UIImageView's Content Mode shouldn't be Aspect fit
The image scaling isn't as linear as I taught
This is all I could think of but how would I go to check these?

Resources