func is reseting position of pan gesture - swift

The func addBlackView is adding a black view everytime the func is called. The black view is connected a to uiPangesture the problem is evertyime the func addblackview is called the code is reseting the position of wherever the first black has been moved. You can see what is goin on in the gif below. I just want the 1st black view to not move and stay in the same position if a new black view is Called.
import UIKit
class ViewController: UIViewController {
var image1Width2: NSLayoutConstraint!
var iHieght: NSLayoutConstraint!
var currentView = UIView()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(currentView)
view.addSubview(button)
button.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
button.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16),
button.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 16),
button.widthAnchor.constraint(equalToConstant: 100),
button.heightAnchor.constraint(equalToConstant: 80),
])
button.addTarget(self,action: #selector(addBlackView),for: .touchUpInside)
}
let slider:UISlider = {
let slider = UISlider(frame: .zero)
return slider
}()
private lazy var button: UIButton = {
let button = UIButton()
button.backgroundColor = .blue
button.setTitleColor(.white, for: .normal)
button.setTitle("add", for: .normal)
return button
}()
let blackView: UIView = {
let view = UIView()
view.backgroundColor = .black
return view
}()
var count = 0
#objc
private func addBlackView() {
let newBlackView = UIView(frame: CGRect(x: 20, y: 20, width: 100, height: 100)) // whatever frame you want
newBlackView.backgroundColor = .orange
self.view.addSubview(newBlackView)
self.currentView = newBlackView
let recognizer = UIPanGestureRecognizer(target: self, action: #selector(moveView(_:)))
newBlackView.addGestureRecognizer(recognizer)
newBlackView.isUserInteractionEnabled = true
image1Width2 = newBlackView.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.1)
image1Width2.isActive = true
iHieght = newBlackView.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.1)
iHieght.isActive = true
count += 1
newBlackView.tag = (count)
newBlackView.translatesAutoresizingMaskIntoConstraints = false
newBlackView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
newBlackView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
}
#objc private func moveView(_ recognizer: UIPanGestureRecognizer) {
switch recognizer.state {
case .changed:
let translation = recognizer.translation(in: self.view)
recognizer.view!.center = .init(x: recognizer.view!.center.x + translation.x,
y: recognizer.view!.center.y + translation.y)
recognizer.setTranslation(.zero, in: self.view)
default:
break
}
}
}

They always go back to the centre because you have constrained the black (orange) views to the centre:
newBlackView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
newBlackView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
You shouldn't even be able to drag any of the views at all, but I guess setting center ignores constraints for some reason. Anyway, when you add a new view, UIKit calls view.setNeedsLayout/layoutIfNeeded somewhere down the line, and this causes all the views to realise "oh wait, I'm supposed to be constrained to the centre!" and snap back. :D
If you want to keep using constraints, try storing the centre X and Y constraints of all the views in an array:
var centerXConstraints: [NSLayoutConstraint] = []
var centerYConstraints: [NSLayoutConstraint] = []
And append to them when you add a new view:
let yConstraint = newBlackView.centerYAnchor.constraint(equalTo: view.centerYAnchor)
let xConstraint = newBlackView.centerXAnchor.constraint(equalTo: view.centerXAnchor)
xConstraint.isActive = true
yConstraint.isActive = true
centerXConstraints.append(xConstraint)
centerYConstraints.append(yConstraint)
Then, rather than changing the center, change the constant of these constraints:
let centerXConstraint = centerXConstraints[recognizer.view!.tag - 1]
let centerYConstraint = centerYConstraints[recognizer.view!.tag - 1]
centerXConstraint.constant += translation.x
centerYConstraint.constant += translation.y
Alternatively, and this is what I would do, just remove all your constraints, and translateAutoresizingMaskIntoConstraints = true. This way you can freely set your center.

Related

align object with changeable constraints to center x point on the view

I want my swift code below to align the box to the center of the x axis. You can see from the gif below of what my code is doing. When the purple button is pressed I would like the box to be align. I am not sure how to d this because some of the constraints are declared as var I dont know where to go next.
import UIKit
class ViewController: UIViewController {
var slizer = UISlider()
var viewDrag = UIImageView()
var b2 = UIButton()
var panGesture = UIPanGestureRecognizer()
// Width, Leading and CenterY constraints for viewDrag
var widthConstraints: NSLayoutConstraint!
var viewDragLeadingConstraint: NSLayoutConstraint!
var viewDragCenterYConstraint: NSLayoutConstraint!
var tim: CGFloat = 50.0
var slidermultiplier: CGFloat = 0.6
override func viewDidLoad() {
super.viewDidLoad()
[viewDrag,slizer,b2].forEach{
$0.translatesAutoresizingMaskIntoConstraints = false
view.addSubview($0)
}
b2.backgroundColor = .purple
NSLayoutConstraint.activate([
b2.bottomAnchor.constraint(equalTo: slizer.topAnchor),
b2.leadingAnchor.constraint(equalTo: view.leadingAnchor),
b2.heightAnchor.constraint(equalTo: view.heightAnchor,multiplier: 0.05),
b2.widthAnchor.constraint(equalTo: view.widthAnchor,multiplier: 1),
slizer.bottomAnchor.constraint(equalTo: view.bottomAnchor),
slizer.leadingAnchor.constraint(equalTo: view.leadingAnchor),
slizer.heightAnchor.constraint(equalTo: view.heightAnchor,multiplier: 0.2),
slizer.widthAnchor.constraint(equalTo: view.widthAnchor,multiplier: 1),
])
slizer.addTarget(self, action: #selector(increase), for: .valueChanged)
viewDrag.backgroundColor = .orange
// no point setting a frame, since
// viewDrag has .translatesAutoresizingMaskIntoConstraints = false
//viewDrag.frame = CGRect(x: view.center.x-view.frame.width * 0.05, y: view.center.y-view.frame.height * 0.05, width: view.frame.width * 0.1, height: view.frame.height * 0.1)
// start with viewDrag
// width = "slidermultiplier" percent of view width
widthConstraints = viewDrag.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: slidermultiplier)
// Leading = "tim" pts from view leading
viewDragLeadingConstraint = viewDrag.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: tim)
// centered vertically
viewDragCenterYConstraint = viewDrag.centerYAnchor.constraint(equalTo: view.centerYAnchor)
NSLayoutConstraint.activate([
// viewDrag height will never change, so we can set it here
viewDrag.heightAnchor.constraint(equalTo: view.heightAnchor,multiplier: 0.3),
// activate the 3 "modifiable" constraints
widthConstraints,
viewDragLeadingConstraint,
viewDragCenterYConstraint,
])
panGesture = UIPanGestureRecognizer(target: self, action: #selector(draggedView(_:)))
viewDrag.isUserInteractionEnabled = true
viewDrag.addGestureRecognizer(panGesture)
// start the slider at the same percentage we've used
// for viewDrag's initial width
slizer.value = Float(slidermultiplier)
b2.addTarget(self, action: #selector(press), for: .touchDown)
}
#objc func press(){
}
#objc func draggedView(_ sender: UIPanGestureRecognizer) {
// old swift syntax
//self.view.bringSubview(toFront: viewDrag)
self.view.bringSubview(toFront: viewDrag)
let translation = sender.translation(in: self.view)
viewDragLeadingConstraint.constant += translation.x
viewDragCenterYConstraint.constant += translation.y
// don't do this
//viewDrag.center = CGPoint(x: viewDrag.center.x + translation.x , y: viewDrag.center.y + translation.y)
sender.setTranslation(CGPoint.zero, in: self.view)
}
#objc func increase() {
// get the new value of the slider
slidermultiplier = CGFloat(slizer.value)
// deactivate widthConstraints
widthConstraints.isActive = false
// create new widthConstraints with slider value as a multiplier
widthConstraints = viewDrag.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: slidermultiplier)
// activate the new widthConstraints
widthConstraints.isActive = true
}
}
I recommend to work directly with frame not constraints.
Constraints are suitable when to deal with mutable screen sizes and screen rotation.
Step 1: Put your movable objects into a container view
Step 2: Handle objects by coordinate inside container view
Step 3: Add container to your view controller's view and constraint layouting it with other elements
class ViewController: UIViewController {
#IBOutlet weak var container: Container!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
container.addPanGestures()
}
#IBAction func didTapLeftButton(_ sender: Any) {
container.setLeftAlign()
}
#IBAction func didTapCenterButton(_ sender: Any) {
container.setCenterXAlign()
}
#IBAction func didTapRightButton(_ sender: Any) {
container.setRightAlign()
}
#IBAction func slide(_ sender: UISlider) {
let scale = CGFloat(max(sender.value, 0.1))
container.setWidthScale(scale)
}
}
class Container: UIView {
func setLeftAlign() {
for view in self.subviews {
view.frame.origin = CGPoint(x: 0, y: view.frame.origin.y)
}
}
func setRightAlign() {
for view in self.subviews {
view.frame.origin = CGPoint(x: self.frame.width - view.frame.width, y:view.frame.origin.y)
}
}
func setCenterXAlign() {
for view in self.subviews {
var center = view.center
center.x = self.frame.width / 2
view.center = center
}
}
func setWidthScale(_ ratio: CGFloat) {
for view in self.subviews {
var frame = view.frame
let center = view.center
frame.size.width = self.frame.width * ratio
view.frame = frame
view.center = center
}
}
func addPanGestures() {
for v in self.subviews {
let panGesture = UIPanGestureRecognizer(target: self, action: #selector(didPan(_:)))
v.addGestureRecognizer(panGesture)
}
}
private var initCenter: CGPoint!
#objc func didPan(_ sender: UIPanGestureRecognizer) {
let view = sender.view!
let translation = sender.translation(in: view)
switch sender.state {
case .began:
initCenter = view.center
case .changed:
view.center = CGPoint(x: initCenter.x + translation.x, y: initCenter.y + translation.y)
case .cancelled:
view.center = initCenter
default:
return
}
}
}

UIView is Being Over Written Every Time Func is Called

My swift codes goal is to place a uiview every time the button is pressed. In my gif you can see every time the blue button is called it is over written. When the code is pressed the gif should have 2 uiviews in it. You can see the transparent uiview of where the first view disappears. Basically all that is wrong with this code is when the addBlackView is called it should add to the views on the screen basically just like a infinite array.
import UIKit
class ViewController: UIViewController {
var image1Width2: NSLayoutConstraint!
var iHieght: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
view.addSubview(slider)
slider.translatesAutoresizingMaskIntoConstraints = false
slider.value = 0.5
slider.isUserInteractionEnabled = true
NSLayoutConstraint.activate([
slider.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
slider.leadingAnchor.constraint(equalTo: view.leadingAnchor),
slider.heightAnchor.constraint(equalToConstant: 100),
slider.widthAnchor.constraint(equalTo: view.widthAnchor,multiplier: 1),
])
view.addSubview(button)
button.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
button.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16),
button.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 16),
button.widthAnchor.constraint(equalToConstant: 100),
button.heightAnchor.constraint(equalToConstant: 80),
])
button.addTarget(self,action: #selector(addBlackView),for: .touchUpInside)
slider.addTarget(self, action: #selector(increase), for: .allEvents)
}
let slider:UISlider = {
let slider = UISlider(frame: .zero)
return slider
}()
private lazy var button: UIButton = {
let button = UIButton()
button.backgroundColor = .blue
button.setTitleColor(.white, for: .normal)
button.setTitle("add", for: .normal)
return button
}()
let blackView: UIView = {
let view = UIView()
view.backgroundColor = .black
return view
}()
#objc
private func addBlackView() {
self.view.addSubview(blackView)
blackView.translatesAutoresizingMaskIntoConstraints = false
blackView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
blackView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
image1Width2 = blackView.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.1)
image1Width2.isActive = true
iHieght = blackView.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.1)
iHieght.isActive = true
view.layoutIfNeeded()
let recognizer = UIPanGestureRecognizer(target: self, action: #selector(moveView(_:)))
blackView.addGestureRecognizer(recognizer)
}
#objc private func moveView(_ recognizer: UIPanGestureRecognizer) {
switch recognizer.state {
case .began:
print("gesture began")
case .changed:
let translation = recognizer.translation(in: self.view)
recognizer.view!.center = .init(x: recognizer.view!.center.x + translation.x,
y: recognizer.view!.center.y + translation.y)
recognizer.setTranslation(.zero, in: self.view)
default:
break
}
}
#objc func increase() {
image1Width2.constant = CGFloat(slider.value) * view.frame.size.width * 0.10
iHieght.constant = CGFloat(slider.value) * view.frame.size.width * 0.10
}}
The problem is that you're reusing and resetting blackView every time you execute addBlackView, so the changes you've made will be lost (hence why the view goes back in the center after you pressed the button).
You would need to create a complete new view in addBlackView, which would be your 'currentView' that you are manipulating and then add add gesture recognizers to it. Then once you execute addBlackView again, the 'currentView' would be 'validated' (stored in an array or whatever you need to do with it) and then you create another one to manipulate.
Something like this:
private func addBlackView() {
let newBlackView = UIView(frame: CGRect(0, 0, 10, 10)) // whatever frame you want
self.view.addSubview(newBlackView)
self.currentView = newBlackView
}

use uipangesture on a nslayout constraint

My swift code below is using nslayout constraints in view did load. I have tried to place a uipangestureRecognizer on a imageview. To get the image view to move around the uiview controller. Right now If I touch the imageview nothing happens. I understand that I have placed permenent constraints in view did load. I just dont know how to get the imageview to move around. The image view I am trying to move is fight[0].
import UIKit
class ViewController: UIViewController {
var pic = UIImageView()
var draw = UIView()
let fight = (0..<10).map { _ in UIImageView() }
var g2 = UIPanGestureRecognizer()
override func viewDidLoad() {
super.viewDidLoad()
fight[0].image = UIImage(named: "a.png")
g2 = UIPanGestureRecognizer(target: self, action: #selector(ViewController.g1Method))
fight[0].addGestureRecognizer(g2)
fight.forEach{
$0.backgroundColor = .clear
view.addSubview($0)
$0.translatesAutoresizingMaskIntoConstraints = false
}
[pic,draw].forEach{
$0.backgroundColor = .red
view.addSubview($0)
$0.translatesAutoresizingMaskIntoConstraints = false
}
// Do any additional setup after loading the view.
NSLayoutConstraint.activate ([
fight[0].trailingAnchor.constraint(equalTo: view.trailingAnchor, constant :0),
fight[0].topAnchor.constraint(equalTo: view.topAnchor, constant : 50),
fight[0].heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.10, constant: 0),
fight[0].widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.10, constant: 0),
fight[0].leadingAnchor.constraint(equalTo: view.leadingAnchor, constant : 0),
])
}
#objc func g1Method(_ sender: UIPanGestureRecognizer){
self.view.bringSubviewToFront(sender.view!)
let tranistioon = sender.translation(in: self.view)
sender.view!.center = CGPoint(x: sender.view!.center.x + tranistioon.x, y: sender.view!.center.y + tranistioon.y)
sender.setTranslation(CGPoint.zero,in: self.view) }
}
By default, UIImageView do not have user interaction enabled.
Add this line inside viewDidLoad():
fight[0].isUserInteractionEnabled = true
You should now be able to drag that image view around.

Cannot see Buttons in UIScrollView

I was making a list in the form of scrollview in swift where the view consists of various types such as labels, button etc.
However when i added the button to the subview, they were not displayed although all other labels etc were displayed. I also tried messing around in the constraints and anchors.
On the other hand when i added the same button to self.view.addsubview instead of scrollview.addsubview, they were displayed just not scrolling since not a part of the scrollview anymore.
I even removed the label to make sure that the buttons were not being overlapped(didn't work either)
I also tried to see the code in "code debug hierarchy " (3D mode), i couldn't see the button there either even though i had added it
Below is my code with an example of label, scrollview and button. It be great if anyone could provide any insights.....thanks either way....
................scrollview..........................
var editInfoView : UIScrollView = {
let view = UIScrollView()
view.translatesAutoresizingMaskIntoConstraints = false
view.contentSize.height = 700
view.backgroundColor = tableBackGroundColor
view.frame = CGRect(x: 0, y: 220, width: 375, height: 400)
return view
}()
.......................label...................
vehicleNumberLabel.translatesAutoresizingMaskIntoConstraints = false
vehicleNumberLabel.textColor = .white
vehicleNumberLabel.text = "Vehicle Number"
vehicleNumberLabel.textAlignment = .left
editInfoView.addSubview(vehicleNumberLabel)
vehicleNumberLabel.leftAnchor.constraint(equalTo: editInfoView.leftAnchor).isActive = true
vehicleNumberLabel.topAnchor.constraint(equalTo: editInfoView.topAnchor, constant: 100).isActive = true
vehicleNumberLabel.widthAnchor.constraint(equalToConstant: 160).isActive = true
vehicleNumberLabel.heightAnchor.constraint(equalToConstant: 20).isActive = true
.....................button................................
vehicleNumberButton.translatesAutoresizingMaskIntoConstraints = false
vehicleNumberButton.setTitleColor(tableTextColor, for: .normal)
vehicleNumberButton.setTitle("Vehicle Number", for: .normal)
vehicleNumberButton.tintColor = tableTextColor
vehicleNumberButton.backgroundColor = tableTextColor
editInfoView.addSubview(vehicleNumberButton)
vehicleNumberButton.rightAnchor.constraint(equalTo: editInfoView.rightAnchor).isActive = true
vehicleNumberButton.topAnchor.constraint(equalTo: editInfoView.topAnchor, constant: 400).isActive = true
vehicleNumberButton.widthAnchor.constraint(equalToConstant: 600).isActive = true
vehicleNumberButton.heightAnchor.constraint(equalToConstant: 255).isActive = true
Although I cannot determine the root cause of your issue with the code and explanation provided I suspect the frame of your UIScrollView() is zero after viewDidAppear(_:) adding subviews to a CGRect.zero can cause some strange behavior with the layout engine. When we create constraints programmatically we are creating a combination of inequalities, equalities, and priorities to restrict the view to a particular frame. If a the value of these constraint equations is incorrect it changes how your relating views appear. Its good practice to avoid the use of leftAnchor and rightAnchor as well, because views may flip direction based on language (writing direction) and user settings.
ViewController.swift
import UIKit
class ViewController: UIViewController {
var editInfoScrollView : UIScrollView = {
let view = UIScrollView()
view.translatesAutoresizingMaskIntoConstraints = false
view.isUserInteractionEnabled = true
view.alwaysBounceVertical = true
view.isScrollEnabled = true
view.contentSize.height = 700
view.backgroundColor = UIColor.red.withAlphaComponent(0.3)
// Does nothing because `translatesAutoresizingMaskIntoConstraints = false`
// Instead, set the content size after activating constraints in viewDidAppear
//view.frame = CGRect(x: 0, y: 220, width: 375, height: 400)
return view
}()
var vehicleNumberLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.textColor = UIColor.black
label.text = "Vehicle Number"
label.textAlignment = .left
return label
}()
lazy var vehicleNumberButton: UIButton = {
let button = UIButton()
button.translatesAutoresizingMaskIntoConstraints = false
button.tag = 1
button.setTitleColor(UIColor.black, for: .normal)
button.setTitle("Go to Vehicle", for: .normal)
button.tintColor = UIColor.white
button.backgroundColor = UIColor.clear
button.layer.cornerRadius = 30 // about half of button.frame.height
button.layer.borderColor = UIColor.black.cgColor
button.layer.borderWidth = 2.0
button.layer.masksToBounds = true
button.addTarget(self, action: #selector(handelButtons(_:)), for: .touchUpInside)
return button
}()
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = UIColor.white
self.setupSubviews()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.editInfoScrollView.contentSize = CGSize(width: self.view.frame.width, height: 700.0)
}
func setupSubviews() {
self.view.addSubview(editInfoScrollView)
editInfoScrollView.addSubview(vehicleNumberLabel)
editInfoScrollView.addSubview(vehicleNumberButton)
let spacing: CGFloat = 12.0
let constraints:[NSLayoutConstraint] = [
editInfoScrollView.widthAnchor.constraint(equalTo: self.view.widthAnchor),
editInfoScrollView.heightAnchor.constraint(equalToConstant: 400.0),
editInfoScrollView.centerXAnchor.constraint(equalTo: self.view.centerXAnchor),
editInfoScrollView.centerYAnchor.constraint(equalTo: self.view.centerYAnchor, constant: 220.0),
vehicleNumberLabel.leadingAnchor.constraint(equalTo: editInfoScrollView.leadingAnchor, constant: spacing),
vehicleNumberLabel.trailingAnchor.constraint(equalTo: editInfoScrollView.trailingAnchor, constant: -spacing),
vehicleNumberLabel.centerXAnchor.constraint(equalTo: editInfoScrollView.centerXAnchor, constant: -50),
vehicleNumberLabel.heightAnchor.constraint(equalToConstant: 75.0),
vehicleNumberButton.widthAnchor.constraint(equalTo: editInfoScrollView.widthAnchor, multiplier: 0.66),
vehicleNumberButton.heightAnchor.constraint(equalToConstant: 65.0),
vehicleNumberButton.topAnchor.constraint(equalTo: vehicleNumberLabel.bottomAnchor, constant: spacing),
vehicleNumberButton.centerXAnchor.constraint(equalTo: editInfoScrollView.centerXAnchor),
]
NSLayoutConstraint.activate(constraints)
}
#objc func handelButtons(_ sender: UIButton) {
switch sender.tag {
case 0:
print("Default button tag")
case 1:
print("vehicleNumberButton was tapped")
default:
print("Nothing here yet")
}
}
}

textview shakes when resizing view

I'm resizing the view that a textview belongs to and the text shakes when the view either gets bigger or gets smaller.
Declaration of said text view:
lazy var textview: UITextView = {
let textView = UITextView()
textView.text = ""
textView.font = .systemFont(ofSize: 12, weight: UIFontWeightMedium)
textView.isScrollEnabled = false
textView.isEditable = false
textView.isSelectable = true
textView.isUserInteractionEnabled = true
textView.translatesAutoresizingMaskIntoConstraints = false
textView.textAlignment = .center
textView.textColor = .lightGray
textView.dataDetectorTypes = .link
return textView
}()
I'm resizing the view that it's in to fit the full screen like this
if let window = UIApplication.shared.keyWindow {
let statusBarHeight = UIApplication.shared.statusBarFrame.size.height
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveLinear, animations: {
self.frame = CGRect(x: 0, y: statusBarHeight, width: window.frame.width, height: window.frame.height - statusBarHeight)
self.layer.cornerRadius = 0
self.layoutIfNeeded()
}, completion: nil)
}
Upon doing so, the view expands perfectly but the textviews text does a bounce effect that makes the animation look extremely unprofessional... any advice?
Edit: It seems like when I remove the center text alignment option it works fine. How do I make it work with the text center aligned?
edit: I took another look at this and attempted to use the technique based in UIScrollView animation of height and contentOffset "jumps" content from bottom.
Here's a minimal working example with text view with centered text alignment which is working for me!
I'd recommend managing animations either to be all constraint based, or all frame based. I attempted a version where the animation is driven by updating the container view frame but it was starting to take too long to left it at this constraint based approach.
Hope this points you in the right direction :)
import UIKit
class ViewController: UIViewController {
lazy var textView: UITextView = {
let textView = UITextView()
textView.text = "testing text view"
textView.textAlignment = .center
textView.translatesAutoresizingMaskIntoConstraints = false
return textView
}()
lazy var containerView: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
var widthConstraint: NSLayoutConstraint!
var topAnchor: NSLayoutConstraint!
override func viewDidLoad() {
view.backgroundColor = .groupTableViewBackground
// add container view and constraints
view.addSubview(containerView)
containerView.frame = view.bounds.insetBy(dx: 100, dy: 200)
containerView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
containerView.heightAnchor.constraint(equalToConstant: 100).isActive = true
// keep reference to topAnchor and width as properties to animate
topAnchor = containerView.topAnchor.constraint(lessThanOrEqualTo: view.topAnchor, constant: 100)
widthConstraint = containerView.widthAnchor.constraint(equalToConstant: 300)
topAnchor.isActive = true
widthConstraint.isActive = true
// add text view to container view and set constraints
containerView.addSubview(textView)
textView.leftAnchor.constraint(equalTo: containerView.leftAnchor).isActive = true
textView.rightAnchor.constraint(equalTo: containerView.rightAnchor).isActive = true
textView.topAnchor.constraint(equalTo: containerView.topAnchor).isActive = true
textView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor).isActive = true
}
#IBAction func toggleResize(_ sender: UIButton) {
sender.isSelected = !sender.isSelected
view.layoutIfNeeded()
widthConstraint.constant = sender.isSelected ? view.bounds.width : 300
topAnchor.constant = sender.isSelected ? 20 : 100
// caculate the textView content offset for starting position based on
// expected end position at end of the animation
let xOffset = (textView.bounds.width - widthConstraint.constant) / 2
textView.contentOffset = CGPoint(x: -xOffset, y: textView.contentOffset.y)
UIView.animate(withDuration: 1) {
self.view.layoutIfNeeded()
}
}
}