UIScrollView with Embedded UIImageView; how to get the image to fill the screen - swift

UIKit/Programmatic UI
I have an UIScrollView with an UIImageView inside. The image is set by user selection and can have all kinds of sizes. What I want is that the image initially fills the screen (view) and then can be zoomed and scrolled all the way to the edges of the image.
If I add the ImageView directly to the view (no scrollView), I get it to fill the screen with the following code:
mapImageView.image = ProjectImages.projectDefaultImage
mapImageView.translatesAutoresizingMaskIntoConstraints = false
mapImageView.contentMode = .scaleAspectFill
view.addSubview(mapImageView)
Now the same with the scrollView and the embedded imageView:
view.insertSubview(mapImageScrollView, at: 0)
mapImageScrollView.delegate = self
mapImageScrollView.translatesAutoresizingMaskIntoConstraints = false
mapImageScrollView.contentMode = .scaleAspectFill
mapImageScrollView.maximumZoomScale = 4.0
mapImageScrollView.pinToEdges(of: view, safeArea: true)
mapImageView.image = ProjectImages.projectDefaultImage
mapImageView.translatesAutoresizingMaskIntoConstraints = false
mapImageView.contentMode = .scaleAspectFill
mapImageScrollView.addSubview(mapImageView)
And now, if the image's height is smaller than the view's height, the image does not fill the screen and I'm left with a blank view area below the image. I can zoom and scroll ok, and then the image does fill the view.
Adding contsraints will fill the view as I want, but interferes with the zooming and scrolling and prevents me getting to the edges of the image when zoomed in.
How to set this up correctly ?

You might find this useful...
It allows you to zoom an image in a scrollView, starting with it centered and maintaining aspect ratio.
Here's a complete implementation. It has two important variables at the top:
// can be .scaleAspectFill or .scaleAspectFit
var fitMode: UIView.ContentMode = .scaleAspectFill
// if fitMode is .scaleAspectFit, allowFullImage is ignored
// if fitMode is .scaleAspectFill, image will start zoomed to .scaleAspectFill
// if allowFullImage is false, image will zoom back to .scaleAspectFill if "pinched in"
// if allowFullImage is true, image can be "pinched in" to see the full image
var allowFullImage: Bool = true
Everything is done via code - no #IBOutlet or other connections - so just create add a new view controller and assign its custom class to ZoomAspectViewController (and edit the name of the image you want to use):
class ZoomAspectViewController: UIViewController, UIScrollViewDelegate {
var scrollView: UIScrollView!
var imageView: UIImageView!
var imageViewBottomConstraint: NSLayoutConstraint!
var imageViewLeadingConstraint: NSLayoutConstraint!
var imageViewTopConstraint: NSLayoutConstraint!
var imageViewTrailingConstraint: NSLayoutConstraint!
// can be .scaleAspectFill or .scaleAspectFit
var fitMode: UIView.ContentMode = .scaleAspectFit
// if fitMode is .scaleAspectFit, allowFullImage is ignored
// if fitMode is .scaleAspectFill, image will start zoomed to .scaleAspectFill
// if allowFullImage is false, image will zoom back to .scaleAspectFill if "pinched in"
// if allowFullImage is true, image can be "pinched in" to see the full image
var allowFullImage: Bool = true
override func viewDidLoad() {
super.viewDidLoad()
guard let img = UIImage(named: "myImage") else {
fatalError("Could not load the image!!!")
}
scrollView = UIScrollView()
imageView = UIImageView()
scrollView.translatesAutoresizingMaskIntoConstraints = false
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.contentMode = .scaleToFill
scrollView.addSubview(imageView)
view.addSubview(scrollView)
// respect safe area
let g = view.safeAreaLayoutGuide
imageViewTopConstraint = imageView.topAnchor.constraint(equalTo: scrollView.topAnchor)
imageViewBottomConstraint = imageView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor)
imageViewLeadingConstraint = imageView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor)
imageViewTrailingConstraint = imageView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor)
NSLayoutConstraint.activate([
scrollView.topAnchor.constraint(equalTo: g.topAnchor),
scrollView.bottomAnchor.constraint(equalTo: g.bottomAnchor),
scrollView.leadingAnchor.constraint(equalTo: g.leadingAnchor),
scrollView.trailingAnchor.constraint(equalTo: g.trailingAnchor),
imageViewTopConstraint,
imageViewBottomConstraint,
imageViewLeadingConstraint,
imageViewTrailingConstraint,
])
scrollView.delegate = self
scrollView.minimumZoomScale = 0.1
scrollView.maximumZoomScale = 5.0
imageView.image = img
imageView.frame.size = img.size
}
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
coordinator.animate(alongsideTransition: { _ in
self.updateMinZoomScaleForSize(size, shouldSize: (self.scrollView.zoomScale == self.scrollView.minimumZoomScale))
self.updateConstraintsForSize(size)
}, completion: {
_ in
})
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
updateMinZoomScaleForSize(scrollView.bounds.size)
updateConstraintsForSize(scrollView.bounds.size)
if fitMode == .scaleAspectFill {
centerImageView()
}
}
func updateMinZoomScaleForSize(_ size: CGSize, shouldSize: Bool = true) {
guard let img = imageView.image else {
return
}
var bShouldSize = shouldSize
let widthScale = size.width / img.size.width
let heightScale = size.height / img.size.height
var minScale = min(widthScale, heightScale)
let startScale = max(widthScale, heightScale)
if fitMode == .scaleAspectFill && !allowFullImage {
minScale = startScale
}
if scrollView.zoomScale < minScale {
bShouldSize = true
}
scrollView.minimumZoomScale = minScale
if bShouldSize {
scrollView.zoomScale = fitMode == .scaleAspectFill ? startScale : minScale
}
}
func scrollViewDidZoom(_ scrollView: UIScrollView) {
updateConstraintsForSize(scrollView.bounds.size)
}
func centerImageView() -> Void {
let yOffset = (scrollView.frame.size.height - imageView.frame.size.height) / 2
let xOffset = (scrollView.frame.size.width - imageView.frame.size.width) / 2
scrollView.contentOffset = CGPoint(x: -xOffset, y: -yOffset)
}
func updateConstraintsForSize(_ size: CGSize) {
let yOffset = max(0, (size.height - imageView.frame.height) / 2)
imageViewTopConstraint.constant = yOffset
imageViewBottomConstraint.constant = yOffset
let xOffset = max(0, (size.width - imageView.frame.width) / 2)
imageViewLeadingConstraint.constant = xOffset
imageViewTrailingConstraint.constant = xOffset
view.layoutIfNeeded()
}
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return imageView
}
}
Edit
As an example, I used this image (2560 x 1440):
and I get this result on launch:
and maximum zoom in (5.0) scrolled to top-center:
Edit 2
Same image, at launch, with:
var fitMode: UIView.ContentMode = .scaleAspectFill
instead of .scaleAspectFit:

I've found this solution that works for me, when setting and changing the image, I calculate the minimum needed zoom scale and set it on the scrollView:
var selectedMapImage: MapImage? {
didSet {
mapImageView.image = mapImagesController.getImageForMapImage(selectedMapImage!)
mapImageScrollView.minimumZoomScale = view.bounds.height / mapImageView.image!.size.height
mapImageScrollView.setZoomScale(mapImageScrollView.minimumZoomScale, animated: true)
mapImageScrollView.scrollRectToVisible(view.bounds, animated: true)
}
}

Related

UIImage in UIImageView that is in UIScrollView is automatically zoomed in - how to initially view whole UIImage?

I have a UIViewController that has a UIScrollView. Within the latter view there is a UIImageView. I have implemented the UIImageView so that the user can zoom in/out. However, at the beginning I would like the user to see the entire UIImage before deciding to zoom in/out. Currently the UIImage is just automatically enlarged. A lot of the answers on stackOverflow suggested setting the UIImageView.contentMode to scaleAspectFit - i have done this but it has not worked.
class ViewImageViewController: UIViewController, UIScrollViewDelegate {
var imageToPresent: UIImage!
let scrollView: UIScrollView = {
let view = UIScrollView()
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
let imageView: UIImageView = {
let imgView = UIImageView()
imgView.backgroundColor = UIColor.black
imgView.contentMode = .scaleAspectFit
imgView.isUserInteractionEnabled = true
imgView.translatesAutoresizingMaskIntoConstraints = false
return imgView
}()
override func viewDidLoad() {
super.viewDidLoad()
print("ViewImageViewController.viewDidLoad")
view.backgroundColor = UIColor.appGrayForLabels
setupViews()
scrollView.delegate = self
}
private func setupViews(){
view.addSubview(scrollView)
scrollView.minimumZoomScale = 1.0
scrollView.maximumZoomScale = 4.0
scrollView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
scrollView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor).isActive = true
scrollView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor).isActive = true
scrollView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor).isActive = true
guard let img = imageToPresent else{return}
imageView.image = img
scrollView.addSubview(imageView)
imageView.topAnchor.constraint(equalTo: scrollView.topAnchor).isActive = true
imageView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor).isActive = true
imageView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor).isActive = true
imageView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor).isActive = true
}
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
scrollView.isScrollEnabled = true
return imageView
}
You want to set the .minimumZoomScale to the ratio between your image size and the scroll view size, and then set the .zoomScale to that value to start with.
Here's your code, with a few modifications:
class ViewImageViewController: UIViewController, UIScrollViewDelegate {
var imageToPresent: UIImage!
let scrollView: UIScrollView = {
let view = UIScrollView()
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
let imageView: UIImageView = {
let imgView = UIImageView()
imgView.backgroundColor = UIColor.black
imgView.contentMode = .scaleAspectFit
imgView.isUserInteractionEnabled = true
imgView.translatesAutoresizingMaskIntoConstraints = false
return imgView
}()
override func viewDidLoad() {
super.viewDidLoad()
print("ViewImageViewController.viewDidLoad")
if let img = UIImage(named: "background") {
imageToPresent = img
}
view.backgroundColor = .gray // UIColor.appGrayForLabels
setupViews()
scrollView.delegate = self
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
let scrollViewFrame = scrollView.frame
let scaleWidth = scrollViewFrame.size.width / imageToPresent.size.width
let scaleHeight = scrollViewFrame.size.height / imageToPresent.size.height
let minScale = min(scaleWidth, scaleHeight)
scrollView.minimumZoomScale = minScale
scrollView.maximumZoomScale = 4.0
scrollView.zoomScale = minScale
}
private func setupViews(){
scrollView.isScrollEnabled = true
view.addSubview(scrollView)
scrollView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
scrollView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor).isActive = true
scrollView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor).isActive = true
scrollView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor).isActive = true
guard let img = imageToPresent else{return}
imageView.image = img
scrollView.addSubview(imageView)
let g = scrollView.contentLayoutGuide
imageView.topAnchor.constraint(equalTo: g.topAnchor).isActive = true
imageView.bottomAnchor.constraint(equalTo: g.bottomAnchor).isActive = true
imageView.leadingAnchor.constraint(equalTo: g.leadingAnchor).isActive = true
imageView.trailingAnchor.constraint(equalTo: g.trailingAnchor).isActive = true
imageView.widthAnchor.constraint(equalToConstant: imageToPresent.size.width).isActive = true
imageView.heightAnchor.constraint(equalToConstant: imageToPresent.size.height).isActive = true
}
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return imageView
}
}

How to connect Pagecounter to ScrollView

Trying to connect my pagecounter to my scroll view, but for some reason it does not change when the image is scrolled.
Wrote code calculating the image size change in the scroll view.
#IBOutlet weak var myScrollView: UIScrollView!
#IBOutlet weak var pageCounter: UIPageControl!
var page = 0
var myImages = [UIImage]()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
var posX: CGFloat = 0
let scrollWidth = myScrollView.frame.size.width
let scrollHeight = myScrollView.frame.size.height
myScrollView.isPagingEnabled = true
myImages = [#imageLiteral(resourceName: "61763233_2273795276270684_2413481963462590464_o"), #imageLiteral(resourceName: "64431295_2284501085200103_3314220009403711488_n"), #imageLiteral(resourceName: "hrc1"), #imageLiteral(resourceName: "66263822_2295361957447349_1103112725925462016_n"), #imageLiteral(resourceName: "62022794_2274568069526738_785458383527346176_o")]
view.setNeedsLayout()
view.layoutIfNeeded()
pageCounter.numberOfPages = myImages.count
pageCounter.pageIndicatorTintColor = UIColor.gray
pageCounter.currentPageIndicatorTintColor = UIColor.blue
for img in myImages {
let imgView = UIImageView(frame: CGRect(x: posX, y: 0, width: scrollWidth, height: scrollHeight))
imgView.image = img
imgView.contentMode = .scaleAspectFill
imgView.clipsToBounds = true
myScrollView.addSubview(imgView)
myScrollView.contentSize = CGSize(width: scrollWidth * CGFloat(myImages.count), height: scrollHeight)
posX = posX + scrollWidth
}
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let pageWidth = myScrollView.frame.size.width
let getpage = round(myScrollView.contentOffset.x / pageWidth)
let currentPage = Int(getpage)
page = currentPage
pageCounter.currentPage = page
}
The page counter should change after every scroll.
set your PageCounter Inside your ScrollView
example:
where pageCounter is your object and scrollView is your scrollView
NSLayoutConstraint.activate([
pageCounter.topAnchor.constraint(equalTo: self.scrollView.topAnchor, constant: 15),
pageCounter.leadingAnchor.constraint(equalTo: self.scrollView.leadingAnchor, constant: 30),
pageCounter.heightAnchor.constraint(equalToConstant: 40),
pageCounter.widthAnchor.constraint(equalToConstant: 40)
])
"myScrollview.delegate = self"
this line of code was missing. Making it impossible to connect with the page control

Image in the UIImageView is clipped when width is device width & height is original image's height

I created an ImageViewController, that is used to view long images. (like comics app)
but, as it is UIImageView's width is device-width, height is original size(aspect ratio)
But the image is cropped. How do I do it?
UI Code
fileprivate let scrollView = UIScrollView().then {
$0.translatesAutoresizingMaskIntoConstraints = false
$0.backgroundColor = .yellow
$0.bounces = false
}
fileprivate let stackView = UIStackView().then {
$0.translatesAutoresizingMaskIntoConstraints = false
$0.axis = .vertical
}
// MARK: - View Life Cycle
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(self.scrollView)
self.scrollView.addSubview(self.stackView)
self.scrollView.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
self.stackView.snp.makeConstraints { make in
make.edges.equalTo(self.scrollView)
}
}
Add UIImageView code
for element in lists {
let imgView = UIImageView()
imgView.setImage(element) // set image from url
imgView.contentMode = .scaleAspectFill
imgView.clipsToBounds = true
imgView.translatesAutoresizingMaskIntoConstraints = false
self.stackView.addArrangedSubview(imgView)
imgView.widthAnchor.constraint(equalTo: self.scrollView.widthAnchor, multiplier: 1.0).isActive = true
imgView.heightAnchor.constraint(equalTo: self.scrollView.heightAnchor, multiplier: 1.0).isActive = true
}
How to aspect ratio(max width) UIImageView?
I found solution!
added below code,
func imageScaled(with sourceImage: UIImage?, scaledToWidth i_width: Float) -> UIImage? {
let oldWidth = Float(sourceImage?.size.width ?? 0.0)
let scaleFactor: Float = i_width / oldWidth
let newHeight = Float((sourceImage?.size.height ?? 0.0) * CGFloat(scaleFactor))
let newWidth: Float = oldWidth * scaleFactor
print(newHeight)
UIGraphicsBeginImageContext(CGSize(width: CGFloat(newWidth), height: CGFloat(newHeight)))
sourceImage?.draw(in: CGRect(x: 0, y: 0, width: CGFloat(newWidth), height: CGFloat(newHeight)))
let newImage: UIImage? = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return newImage
}
then, set to UIImageView.
imgView.setImageScaled(element, width: Float(self.scrollView.frame.width))
as your image is bigger in height then the iphone's display height you have to put your image view in the scrollView and set content mode to .scaleAspectFit and set imageView's height equal to image's height
imgView.contentMode = .scaleAspectFit

Zoomable fullscreen image in portrait and landscape mode

I am a beginner at programming. I have an app where you can tap into any of the images that are on display to show that image in full-screen mode and can pinch to zoom. The issue I am having is that if you rotate the phone then the image is only half visible.
I'm using a scroll view to achieve the zoom functionality as that seems to be the consensus of the best way to do it.
It works perfectly in portrait mode, or if I enter the fullscreen image while the app is already in landscape orientation, but If I go into the landscape while in the fullscreen image that's where it goes wrong. Here is the code:
class PictureDetailViewController: UIViewController, UIScrollViewDelegate {
#IBOutlet weak var scrollView: UIScrollView!
var routeData = IndividualRoute(numberOfRoute: UserDefaults.standard.integer(forKey: "currentRoute"))
var detailPicture = UserDefaults.standard.bool(forKey: "segueFromDetailvc")
var detailImage = UIImageView()
override func viewDidLoad() {
super.viewDidLoad()
routeData.routesValues()
scrollView.delegate = self
selectImage()
scrollView.frame = UIScreen.main.bounds
scrollView.addSubview(detailImage)
scrollViewContents()
setupConstraints()
let scrollViewFrame = scrollView.frame
let scaleWidth = scrollViewFrame.size.width / scrollView.contentSize.width
let scaleHieght = scrollViewFrame.size.height / scrollView.contentSize.height
let minScale = min(scaleHieght, scaleWidth)
scrollView.minimumZoomScale = minScale
scrollView.maximumZoomScale = 1
scrollView.zoomScale = minScale
let tap = UITapGestureRecognizer(target: self, action: #selector(dismissFullscreen))
tap.numberOfTapsRequired = 2
view.addGestureRecognizer(tap)
}
//****************************************
//Image Setup
func setupConstraints() {
scrollView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
scrollView.topAnchor.constraint(equalTo: view.topAnchor),
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
scrollView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
scrollView.centerYAnchor.constraint(equalTo: view.centerYAnchor)])
scrollView.contentSize = (detailImage.image?.size)!
}
func selectImage() {
if !detailPicture {
let foo = "\(routeData.achievements[UserDefaults.standard.integer(forKey: "currentAchievement")])"
detailImage.image = UIImage(named: "\(foo.folding(options: .diacriticInsensitive, locale: nil)) 0")
} else {
let foo = "\(routeData.achievements[0])"
detailImage.image = UIImage(named: "\(foo.folding(options: .diacriticInsensitive, locale: nil)) 0")
print("\(foo.folding(options: .diacriticInsensitive, locale: nil)) 0")
}
guard let width = detailImage.image?.size.width else {
return
}
guard let height = detailImage.image?.size.height else {
return
}
let frame: CGRect = CGRect(x: 0, y: 0, width: width, height: height)
detailImage.frame = frame
detailImage.isUserInteractionEnabled = true
}
//**************************************
//Scrollview setup
#objc func dismissFullscreen(){
scrollView.setZoomScale(1, animated: true)
}
func scrollViewContents() {
let boundSize = UIScreen.main.bounds.size
var contentFrame = detailImage.frame
if contentFrame.size.width < boundSize.width {
contentFrame.origin.x = (boundSize.width - contentFrame.size.width) / 2
} else {
contentFrame.origin.x = 0
}
if contentFrame.size.height < boundSize.height {
contentFrame.origin.y = (boundSize.height - contentFrame.size.height) / 2
} else {
contentFrame.origin.y = 0
}
detailImage.frame = contentFrame
}
func scrollViewDidZoom(_ scrollView: UIScrollView) {
scrollViewContents()
}
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return detailImage
}
Sorry for posting so much code but its pretty much all relevant (I think).
Here are screenshots of the problem:
Okay, I've managed to fix it! Had a little jump around with delight.
I set up a new function called setupScale() that is called in viewdidload when the view is presented. I also added the viewwillLayoutSubview() override and called the setupScale() function inside it.
If looks like this:
private func setupScale() {
scrollView.frame = UIScreen.main.bounds
scrollView.contentSize = (detailImage.image?.size)!
scrollViewContents()
let scrollViewFrame = scrollView.frame
let scaleWidth = scrollViewFrame.size.width / scrollView.contentSize.width
let scaleHieght = scrollViewFrame.size.height / scrollView.contentSize.height
let minScale = min(scaleHieght, scaleWidth)
scrollView.minimumZoomScale = minScale
scrollView.maximumZoomScale = 1
scrollView.zoomScale = minScale
}
override func viewWillLayoutSubviews() {
setupScale()
}
Results look perfect on my iPad and iPhone 7 in landscape and portrait.

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