Removing a UIView from UIStackView Changes its Size - swift

I am trying to build a drag drop action and if I drag a view from stack view and drop it to somewhere and remove the view with "removeArrangedSubview" it changes the dragged items size and makes it bigger. I am using this piece of code to resize dropped item.
sender.view!.frame = CGRectMake(sender.view!.frame.origin.x, sender.view!.frame.origin.y, sender.view!.frame.width * 0.5, sender.view!.frame.height * 0.5)
Here is the image for the comparison.

You are setting the frame of the view you are moving but when you place it in a stack view the stack view will reset it based on the stack view's constraints and the intrinsic content size of the view that you are adding to it. The reason it is not doing that when you don't call removeArrangedSubview is because it has not triggered an auto layout pass. In general, when you're using auto layout you shouldn't set views frames but rather update constraints intrinsicContentSize and request auto layout updates.
You should play around with the distribution property of your stack view, the constraints you have placed on it, as well as the intrinsicContentSize of the views you are adding to it until you get your desired result.
For example:
class ViewController: UIViewController
{
let stackView = UIStackView()
let otherStackView = UIStackView()
override func viewDidLoad()
{
self.view.backgroundColor = UIColor.whiteColor()
stackView.alignment = .Center
stackView.axis = .Horizontal
stackView.spacing = 10.0
stackView.distribution = .FillEqually
stackView.translatesAutoresizingMaskIntoConstraints = false
otherStackView.alignment = .Center
otherStackView.axis = .Horizontal
otherStackView.spacing = 10.0
otherStackView.distribution = .FillEqually
otherStackView.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(stackView)
self.view.addSubview(otherStackView)
stackView.bottomAnchor.constraintEqualToAnchor(self.view.bottomAnchor).active = true
stackView.leadingAnchor.constraintEqualToAnchor(self.view.leadingAnchor).active = true
stackView.trailingAnchor.constraintEqualToAnchor(self.view.trailingAnchor).active = true
stackView.heightAnchor.constraintEqualToConstant(150).active = true
otherStackView.topAnchor.constraintEqualToAnchor(self.view.topAnchor).active = true
otherStackView.widthAnchor.constraintGreaterThanOrEqualToConstant(50).active = true
otherStackView.centerXAnchor.constraintEqualToAnchor(self.view.centerXAnchor).active = true
otherStackView.heightAnchor.constraintEqualToConstant(150).active = true
self.addSubviews()
}
func addSubviews()
{
for _ in 0 ..< 5
{
let view = View(frame: CGRect(x: 0.0, y: 0.0, width: 100, height: 100))
view.userInteractionEnabled = true
let panGesture = UIPanGestureRecognizer(target: self, action: #selector(self.panHandler(_:)))
view.addGestureRecognizer(panGesture)
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = UIColor.redColor()
self.stackView.addArrangedSubview(view)
}
}
func panHandler(sender: UIPanGestureRecognizer)
{
guard let view = sender.view else{ return }
switch sender.state {
case .Began:
self.stackView.removeArrangedSubview(view)
self.view.addSubview(view)
let location = sender.locationInView(self.view)
view.center = location
case .Changed:
view.center.x += sender.translationInView(self.view).x
view.center.y += sender.translationInView(self.view).y
case .Ended:
if CGRectContainsPoint(self.otherStackView.frame, view.center)
{
self.otherStackView.addArrangedSubview(view)
self.otherStackView.layoutIfNeeded()
}
else
{
self.stackView.addArrangedSubview(view)
self.otherStackView.layoutIfNeeded()
}
default:
self.stackView.addArrangedSubview(view)
self.otherStackView.layoutIfNeeded()
}
sender.setTranslation(CGPointZero, inView: self.view)
}
}
class View: UIView
{
override func intrinsicContentSize() -> CGSize
{
return CGSize(width: 100, height: 100)
}
}
The above view controller and UIView subclass does something similar to what you're looking for, but it's not possible to tell from your question. Notice that pinning a stack view to the edges of its super view (like stackView) causes it to stretch subviews to fill all the available space while allowing the stack view to dynamically size based on the intrinsic size of its subviews (like otherStackView) does not.
Update
If you don't want to add the view to another stack view but you want them to retain their frame, you should remove any constraints the view's have on them and then set their translatesAutoresizingMaskIntoConstraints property to true. For example:
func panHandler(sender: UIPanGestureRecognizer)
{
guard let view = sender.view else{ return }
switch sender.state {
case .Began:
self.stackView.removeArrangedSubview(view)
self.view.addSubview(view)
let location = sender.locationInView(self.view)
view.center = location
case .Changed:
view.center.x += sender.translationInView(self.view).x
view.center.y += sender.translationInView(self.view).y
default:
view.removeConstraints(view.constraints)
view.translatesAutoresizingMaskIntoConstraints = true
// You can now set the view's frame or position however you want
view.center.x += sender.translationInView(self.view).x
view.center.y += sender.translationInView(self.view).y
}
sender.setTranslation(CGPointZero, inView: self.view)
}

Related

add container to another container - problem with container.frame.origin

I am testing pan Gesture Recognizer.
I add a white Container to view and then add another red container to the white container.
When I am printing the whiteContainer.frame.orgin I get correct CGPoint numbers but when I print redContaner CGPoint numbers I get 0.0, 0.0
class ViewController6 : UIViewController {
let whiteContainer = UIView()
let redContainer = UIView()
var pointOrigin2: CGPoint?
var pointOrigin3: CGPoint?
override func viewDidLoad() {
super.viewDidLoad()
setupView()
}
override func viewDidLayoutSubviews() {
pointOrigin2 = whiteContainer.frame.origin
print(pointOrigin2!)
pointOrigin3 = redContainer.frame.origin
print(pointOrigin3!)
}
private func setupView() {
view.backgroundColor = .black
configureContainer()
configureRedContainer()
addPanGestureRecognizare()
}
func configureContainer() {
view.addSubview(whiteContainer)
whiteContainer.translatesAutoresizingMaskIntoConstraints = false
whiteContainer.backgroundColor = .white
NSLayoutConstraint.activate([
whiteContainer.centerXAnchor.constraint(equalTo: view.centerXAnchor),
whiteContainer.centerYAnchor.constraint(equalTo: view.centerYAnchor),
whiteContainer.widthAnchor.constraint(equalToConstant: 200),
whiteContainer.heightAnchor.constraint(equalToConstant: 200)
])
}
func configureRedContainer() {
whiteContainer.addSubview(redContainer)
redContainer.translatesAutoresizingMaskIntoConstraints = false
redContainer.backgroundColor = .red
NSLayoutConstraint.activate([
redContainer.centerXAnchor.constraint(equalTo: view.centerXAnchor),
redContainer.centerYAnchor.constraint(equalTo: view.centerYAnchor),
redContainer.widthAnchor.constraint(equalToConstant: 50),
redContainer.heightAnchor.constraint(equalToConstant: 50)
])
}
When I am adding red Container to view (not whiteContainer.addSubview(red Container) ) I get the correct CGPoint number of the white and red container.
Why ?
Viewcontroller hasn't any calculation for the frames for subviews is not in own view if you add in viewDidLoad. If you add a subview to ViewController's view , then yes ViewController makes calculation for that view but if you add subview to view's subview , it doesnt. Thats why you can see the whiteContainer frame is not zero and redContainer is zero.
To make this you should say 'view , you should calculation it'. Thats why you must use view.layoutIfNeeded() after the constraints settings .
This gonna fix your problem
func configureRedContainer() {
whiteContainer.addSubview(redContainer)
redContainer.translatesAutoresizingMaskIntoConstraints = false
redContainer.backgroundColor = .red
NSLayoutConstraint.activate([
redContainer.centerXAnchor.constraint(equalTo: view.centerXAnchor),
redContainer.centerYAnchor.constraint(equalTo: view.centerYAnchor),
redContainer.widthAnchor.constraint(equalToConstant: 50),
redContainer.heightAnchor.constraint(equalToConstant: 50)
])
view.layoutIfNeeded() // must add after setting constraints
}
now my func PanGestute is working incorrectly. the condition "outside the white container" is triggered all the time
#OmerTekbiyik ?
func addPanGestureRecognizare() {
let pan = UIPanGestureRecognizer(target: self, action: #selector(handlePanGesture2(sender:)))
redContainer.addGestureRecognizer(pan)
}
#objc func handlePanGesture2(sender : UIPanGestureRecognizer) {
let fileView = sender.view!
switch sender.state {
case .began, .changed:
let translation = sender.translation(in: fileView)
fileView.center = CGPoint(x: fileView.center.x + translation.x, y: fileView.center.y + translation.y)
sender.setTranslation(CGPoint.zero, in: fileView)
case .ended:
if fileView.frame.intersects(whiteContainer.frame) {
print("in white container")
} else {
print("outside the white container")
UIView.animate(withDuration: 0.3, animations: { fileView.frame.origin = self.pointOrigin3!})
}
default:
break
}
}

func is reseting position of pan gesture

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.

Scroll view paging content size doesn't work with iphone 5s ios 12

I have an horizontal paging scroll view with 3 images.
This is the code in my viewDidLoad:
for index in 0..<images.count {
frame.origin.x = scrollView.frame.size.width * CGFloat(index)
frame.size = scrollView.frame.size
let UrlImage = NSURL(string: images[index] as! String)
let dataImage = NSData(contentsOf: UrlImage! as URL)
if dataImage != nil {
let imgView = UIImageView(frame: frame)
imgView.image = UIImage(data: dataImage! as Data)
imgView.contentMode = .scaleAspectFit
self.scrollView.backgroundColor = .lightGray
self.scrollView.addSubview(imgView)
}
}
scrollView.contentSize = CGSize(width: (scrollView.frame.size.width * CGFloat(images.count)), height: scrollView.frame.size.height)
scrollView.delegate = self
and this is the result:
If i try with last iphone devices it works properly. But if i try with iphone 5s with ios12, content size doesn't work and this is the result:
How can i fix this? I'v been trying for 3 days
I'm assuming you have code to handle the page control and scroll delegate(s)...
Here's a very simple example of using a UIStackView to hold the 3 image views:
class ViewController: UIViewController {
let scrollView = UIScrollView()
let stack = UIStackView()
let pageControl = UIPageControl()
override func viewDidLoad() {
super.viewDidLoad()
// scroll view properties
scrollView.backgroundColor = .gray
scrollView.isPagingEnabled = true
// stack view properties
stack.axis = .horizontal
stack.alignment = .fill
stack.distribution = .fillEqually
stack.spacing = 0
// page control properties
pageControl.backgroundColor = .white
pageControl.pageIndicatorTintColor = .darkGray
pageControl.currentPageIndicatorTintColor = .blue
// we're going to use constraints
scrollView.translatesAutoresizingMaskIntoConstraints = false
stack.translatesAutoresizingMaskIntoConstraints = false
pageControl.translatesAutoresizingMaskIntoConstraints = false
scrollView.addSubview(stack)
view.addSubview(scrollView)
view.addSubview(pageControl)
// respect safe area
let g = view.layoutMarginsGuide
NSLayoutConstraint.activate([
// scroll view constrained Top / Leading / Trailing
scrollView.topAnchor.constraint(equalTo: g.topAnchor),
scrollView.leadingAnchor.constraint(equalTo: g.leadingAnchor),
scrollView.trailingAnchor.constraint(equalTo: g.trailingAnchor),
// scroll view height = 1/2 of view height
scrollView.heightAnchor.constraint(equalTo: g.heightAnchor, multiplier: 0.5),
// stack view constrained Top / Bottom / Leading / Trailing of scroll view
stack.topAnchor.constraint(equalTo: scrollView.topAnchor),
stack.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor),
stack.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor),
stack.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor),
// stack view height == scroll view height
stack.heightAnchor.constraint(equalTo: scrollView.heightAnchor),
// page control centered horizontally just below the scroll view
pageControl.topAnchor.constraint(equalTo: scrollView.bottomAnchor, constant: 4.0),
pageControl.centerXAnchor.constraint(equalTo: scrollView.centerXAnchor),
])
// add three image views to the stack view
for _ in 1...3 {
let v = UIImageView()
v.backgroundColor = .lightGray
v.contentMode = .scaleAspectFit
stack.addArrangedSubview(v)
}
// stack distribution is set to .fillEqually, so we only need to set the
// width constraint on the first image view
// unwrap it
if let v = stack.arrangedSubviews.first {
// stack view has 8-pts on each side
v.widthAnchor.constraint(equalTo: scrollView.widthAnchor).isActive = true
}
pageControl.numberOfPages = 3
// set delegates...
// implement page control funcs...
// etc...
// start image retrieval process
downloadImages()
}
func downloadImages() -> Void {
// get 3 "placeholder" images
// 400x300 400x400 300x400
let urls: [String] = [
"https://via.placeholder.com/400x300.png/FF0000/FFF",
"https://via.placeholder.com/400x400.png/00AA00/FFF",
"https://via.placeholder.com/300x400.png/0000FF/FFF",
]
for (v, urlString) in zip(stack.arrangedSubviews, urls) {
if let imgView = v as? UIImageView, let url = URL(string: urlString) {
imgView.load(url: url)
}
}
}
}
// async download image url from: https://www.hackingwithswift.com/example-code/uikit/how-to-load-a-remote-image-url-into-uiimageview
extension UIImageView {
func load(url: URL) {
DispatchQueue.global().async { [weak self] in
if let data = try? Data(contentsOf: url) {
if let image = UIImage(data: data) {
DispatchQueue.main.async {
self?.image = image
}
}
}
}
}
}

Animate UIView position on tableview scroll and again tableview drag

I have a list of items which a header menu at the top.
When the user scrolls up the menu should scroll off screen also and when they return to the top of the list the menu should be visible.
In much the same way as a tableViewHeader behaves.
However, should the header be off screen and the user ends a drag down on the list, this header view should animate down from the top. If the user then ends a drag up on the list, the header should animate back off screen.
I have achieved the first part of this below, however am struggling to achieve the animation effect.
I had considered using 2 views for the menu, one that scrolls and one that animates it's position, however this feels off, I'm sure there must be a better way without duplicating views.
class TableViewScene: UIViewController {
let data = Array(0...99)
var headerViewOneTopConstraint: NSLayoutConstraint!
var tableViewTopConstraint: NSLayoutConstraint!
lazy var headerViewOne: UIView = {
let view = UIView(frame: .zero)
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = .purple
return view
}()
lazy var tableView: UITableView = {
let view = UITableView(frame: .zero)
view.translatesAutoresizingMaskIntoConstraints = false
view.delegate = self
view.dataSource = self
view.tableFooterView = .init()
view.refreshControl = .init()
return view
}()
override func viewDidLoad() {
super.viewDidLoad()
navigationController?.navigationBar.isTranslucent = false
headerViewOneTopConstraint = headerViewOne.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor)
tableViewTopConstraint = tableView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 184)
[headerViewOne, tableView].forEach(view.addSubview)
NSLayoutConstraint.activate([
headerViewOneTopConstraint,
headerViewOne.leadingAnchor.constraint(equalTo: view.leadingAnchor),
headerViewOne.trailingAnchor.constraint(equalTo: view.trailingAnchor),
headerViewOne.heightAnchor.constraint(equalToConstant: 184),
tableViewTopConstraint,
tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor)
])
}
}
extension TableViewScene: UITableViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let offsetY = scrollView.contentOffset.y
headerViewOneTopConstraint.constant = max(-184, min(0, -offsetY))
tableViewTopConstraint.constant = 184 - max(0, offsetY)
}
}
I had also tried adding this to the view
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
let translation = scrollView.panGestureRecognizer.translation(in: scrollView.superview)
if translation.y > 0 {
headerViewOne.transform = .init(translationX: 0, y: 184)
} else if translation.y < 0 {
headerViewOne.transform = .identity
}
}
This does not work and instead just renders a black space the view used to fill on scroll to the top.
It's hard to say exactly what you are looking for but this might work for you. In this implementation when the user drags up the header will hide and when the user drags down the header will show. This is true regardless of the scroll position.
I made a change to one of your constraints so that the top of the tableView is pinned to the bottom of the header:
tableViewTopConstraint = tableView.topAnchor.constraint(equalTo: headerViewOne.bottomAnchor)
I'm also tracking the state of the header like so:
enum HeaderState {
case hidden
case revealed
case hiding
case revealing
}
var headerState: HeaderState = .revealed
Now for the scrolling logic. If the scroll position is near the top then we want to manually show/hide the header. However, if the scroll position is near the bottom or center then we want to animate the show/hide functionality.
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let translation = scrollView.panGestureRecognizer.translation(in: scrollView.superview)
if scrollView.contentOffset.y < 184 {
if translation.y > 0 && headerState != .revealed && headerState != .revealing {
headerViewOneTopConstraint.constant = max(-184, min(0, -scrollView.contentOffset.y))
} else if translation.y < 0 {
headerViewOneTopConstraint.constant = max(-184, min(0, -scrollView.contentOffset.y))
}
return
}
if translation.y > 0 && headerState == .hidden {
self.headerState = .revealing
UIView.animate(withDuration: 0.4, animations: {
self.headerViewOneTopConstraint.constant = 0
self.view.layoutIfNeeded()
}, completion: { _ in
self.headerState = .revealed
})
} else if translation.y < 0 && headerState == .revealed {
self.headerState = .hiding
UIView.animate(withDuration: 0.4, animations: {
self.headerViewOneTopConstraint.constant = -184
self.view.layoutIfNeeded()
}, completion: { _ in
self.headerState = .hidden
})
}
}
Give this a shot and see if it meets your needs.

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()
}
}
}