Programmatic constraints with screen rotation (swift) - ios

I have 2 UIViews, cv1 and cv2. When in portrait, I want cv1 to occupy the top half of the screen and cv2 the bottom half. When rotated into landscape, I want cv1 to take the left half and cv2 to take the right half, like this:
Here is how I have it set up (the ChildView1 and ChildView2 classes only provide the colors and corner rounding):
class ViewController: UIViewController {
let cv1 = ChildView1()
let cv2 = ChildView2()
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
var safeAreaHeight: CGFloat {
if #available(iOS 11.0, *) {
return view.safeAreaLayoutGuide.layoutFrame.size.height
}
return view.bounds.height
}
//debugPrint("height = \(safeAreaHeight)")
var safeAreaWidth: CGFloat {
if #available(iOS 11.0, *) {
return view.safeAreaLayoutGuide.layoutFrame.size.width
}
return view.bounds.width
}
//debugPrint("width = \(safeAreaWidth)")
cv1.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 0).isActive = true
cv1.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 0).isActive = true
if UIDevice.current.orientation == .portrait || UIDevice.current.orientation == .portraitUpsideDown {
cv1.heightAnchor.constraint(equalToConstant: safeAreaHeight / 2).isActive = true
cv1.widthAnchor.constraint(equalToConstant: safeAreaWidth).isActive = true
cv2.topAnchor.constraint(equalTo: cv1.bottomAnchor, constant: 0).isActive = true
cv2.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 0).isActive = true
//debugPrint("Portrait: height = \(safeAreaHeight), width = \(safeAreaWidth)")
} else if UIDevice.current.orientation == .landscapeLeft || UIDevice.current.orientation == .landscapeRight {
cv1.heightAnchor.constraint(equalToConstant: safeAreaHeight).isActive = true
cv1.widthAnchor.constraint(equalToConstant: safeAreaWidth / 2).isActive = true
cv2.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 0).isActive = true
cv2.leftAnchor.constraint(equalTo: cv1.rightAnchor, constant: 0).isActive = true
//debugPrint("Landscape: height = \(safeAreaHeight), width = \(safeAreaWidth)")
}
cv2.heightAnchor.constraint(equalTo: cv1.heightAnchor).isActive = true
cv2.widthAnchor.constraint(equalTo: cv1.widthAnchor).isActive = true
}
override func viewDidLoad() {
super.viewDidLoad()
addCV1()
addCV2()
}
func addCV1() {
self.view.addSubview(cv1)
cv1.translatesAutoresizingMaskIntoConstraints = false
}
func addCV2() {
self.view.addSubview(cv2)
cv2.translatesAutoresizingMaskIntoConstraints = false
}
}
If I begin in either portrait or landscape, it looks fine. But when I rotate the screen both view disappear and I get the following error message for each constraint:
2020-08-11 10:28:55.328063-0600 RotateScreenTesting[91471:4449618] libMobileGestalt MobileGestalt.c:890: MGIsDeviceOneOfType is not supported on this platform.
"Portrait: height = 603.0, width = 375.0"
2020-08-11 10:29:15.046153-0600 RotateScreenTesting[91471:4449618] [LayoutConstraints] Unable to simultaneously satisfy constraints.
Probably at least one of the constraints in the following list is one you don't want.
Try this:
(1) look at each constraint and try to figure out which you don't expect;
(2) find the code that added the unwanted constraint or constraints and fix it.
(
"<NSLayoutConstraint:0x600003d74500 RotateScreenTesting.ChildView1:0x7fceba30a720.height == 301.5 (active)>",
"<NSLayoutConstraint:0x600003d6d180 RotateScreenTesting.ChildView1:0x7fceba30a720.height == 343 (active)>"
)
Will attempt to recover by breaking constraint
<NSLayoutConstraint:0x600003d6d180 RotateScreenTesting.ChildView1:0x7fceba30a720.height == 343 (active)>
Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger.
The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in <UIKitCore/UIView.h> may also be helpful.
(lldb)
What am I doing wrong???

It is important to remember that we should be working with size layouts, rather than portrait / landscape. With multitasking slide over / split views, it's not unusual to have your view have a different height:width ratio than the device itself.
There are various ways to handle this... here's one approach.
create an array of "wide layout" constraints
create an array of "narrow layout" constraints
create an array of "common" constraints - these apply whether in narrow or wide "orientation"
activate / deactivate the wide and narrow constraints, based on the size of the view
Try this example:
class ChildView1: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() -> Void {
backgroundColor = .blue
layer.cornerRadius = 16
}
}
class ChildView2: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() -> Void {
backgroundColor = .red
layer.cornerRadius = 16
}
}
class SampleViewController: UIViewController {
let cv1 = ChildView1()
let cv2 = ChildView2()
// array of constraints for "wide" layout
var wideConstraints: [NSLayoutConstraint] = []
// array of constraints for "narrow" layout
var narrowConstraints: [NSLayoutConstraint] = []
// just for clarity, array of constraints that apply for
// both wide and narrow layouts
var commonConstraints: [NSLayoutConstraint] = []
override func viewDidLoad() {
super.viewDidLoad()
cv1.translatesAutoresizingMaskIntoConstraints = false
cv2.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(cv1)
view.addSubview(cv2)
let g = view.safeAreaLayoutGuide
commonConstraints = [
// cv1 will always be constrained top and leading
cv1.topAnchor.constraint(equalTo: g.topAnchor),
cv1.leadingAnchor.constraint(equalTo: g.leadingAnchor),
// cv2 will always be constrained trailing and bottom
cv2.trailingAnchor.constraint(equalTo: g.trailingAnchor),
cv2.bottomAnchor.constraint(equalTo: g.bottomAnchor),
]
// when narrow, cv1 on top of cv2
narrowConstraints = [
// constrain cv1 trailing
cv1.trailingAnchor.constraint(equalTo: g.trailingAnchor),
// constrain cv2 leading
cv2.leadingAnchor.constraint(equalTo: g.leadingAnchor),
// constrain cv2 top to cv1 bottom
cv2.topAnchor.constraint(equalTo: cv1.bottomAnchor),
// make them equal heights
cv2.heightAnchor.constraint(equalTo: cv1.heightAnchor),
]
// when wide, cv1 side-by-side cv2
wideConstraints = [
// constrain cv1 bottom
cv1.bottomAnchor.constraint(equalTo: g.bottomAnchor),
// constrain cv2 top
cv2.topAnchor.constraint(equalTo: g.topAnchor),
// constrain cv2 leading to cv1 trailing
cv2.leadingAnchor.constraint(equalTo: cv1.trailingAnchor),
// make them equal widths
cv2.widthAnchor.constraint(equalTo: cv1.widthAnchor),
]
// activate the commonConstraints
NSLayoutConstraint.activate(commonConstraints)
if view.frame.width > view.frame.height {
// wider than tall, so "landscape"
NSLayoutConstraint.activate(wideConstraints)
} else {
// taller than wide
NSLayoutConstraint.activate(narrowConstraints)
}
}
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
coordinator.animate(alongsideTransition: { _ in
if size.width > size.height {
// we're transitioning to wider than tall
NSLayoutConstraint.deactivate(self.narrowConstraints)
NSLayoutConstraint.activate(self.wideConstraints)
} else {
// we're transitioning to taller than wide
NSLayoutConstraint.deactivate(self.wideConstraints)
NSLayoutConstraint.activate(self.narrowConstraints)
}
}, completion: {
_ in
// if you want to do somwthing after the transition
})
}
}

Ref:-
Before Update Constraints Check already applied constraint. if already have constraint get them and update others.
Example:-
//Height
if let heightConstraint = cv1.constraints.first(where: { $0.firstAttribute == .height })
{
heightConstraint.constant = safeAreaHeight
}
else
{
NSLayoutConstraint.activate([
cv1.heightAnchor.constraint(equalToConstant: safeAreaHeight)
])
}
//Width
if let widthConstraint = cv1.constraints.first(where: { $0.firstAttribute == .width })
{
widthConstraint.constant = safeAreaWidth / 2
}
else
{
NSLayoutConstraint.activate([
cv1.widthAnchor.constraint(equalToConstant: safeAreaWidth / 2)
])
}

Related

How to animate movement of UIView between different superViews

Hello I am new at swift and IOS apps , I am trying to animate movement of cardBackImage (UIImage) from deckPileImage to the card view, but everything got different superViews and I have no idea how to do it properly , all the location have different frames ( superviews as described in the Image) , Should I use CGAffineTransform ?
viewHierarchyDescription
try to imagine my abstraction as a "face down card fly from deck into its possition on boardView"
Don't animate the view at all. Instead, animate a snapshot view as a proxy. You can see me doing it here, in this scene from one of my apps.
That red rectangle looks like it's magically flying out of one view hierarchy into another. But it isn't. In reality there are two red rectangles. I hide the first rectangle and show the snapshot view in its place, animate the snapshot view to where the other rectangle is lurking hidden, then hide the snapshot and show the other rectangle.
To help get you going...
First, no idea why you have your "deckPileImage" in a stack view, but assuming you have a reason for doing so...
a simple "card" view - bordered with rounded corners
class CardView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() {
layer.cornerRadius = 16
layer.masksToBounds = true
layer.borderWidth = 1
layer.borderColor = UIColor.black.cgColor
}
}
a basic view controller - adds a "deck pile view" to a stack view, and a "card position view" as the destination for the new, animated cards.
class AnimCardVC: UIViewController {
let deckStackView: UIStackView = UIStackView()
let cardPositionView: UIView = UIView()
let deckPileView: CardView = CardView()
let cardSize: CGSize = CGSize(width: 80, height: 120)
// card colors to cycle through
let colors: [UIColor] = [
.systemRed, .systemGreen, .systemBlue,
.systemCyan, .systemOrange,
]
var colorIDX: Int = 0
// card position constraints to animate
var animXAnchor: NSLayoutConstraint!
var animYAnchor: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBackground
deckStackView.translatesAutoresizingMaskIntoConstraints = false
deckPileView.translatesAutoresizingMaskIntoConstraints = false
cardPositionView.translatesAutoresizingMaskIntoConstraints = false
deckStackView.addArrangedSubview(deckPileView)
view.addSubview(deckStackView)
view.addSubview(cardPositionView)
// always respect safe area
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
deckStackView.topAnchor.constraint(equalTo: g.topAnchor, constant: 40.0),
deckStackView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
// we'll let the stack view subviews determine its size
deckPileView.widthAnchor.constraint(equalToConstant: cardSize.width),
deckPileView.heightAnchor.constraint(equalToConstant: cardSize.height),
cardPositionView.topAnchor.constraint(equalTo: deckStackView.bottomAnchor, constant: 100.0),
cardPositionView.centerXAnchor.constraint(equalTo: g.centerXAnchor),
cardPositionView.widthAnchor.constraint(equalToConstant: cardSize.width + 2.0),
cardPositionView.heightAnchor.constraint(equalToConstant: cardSize.height + 2.0),
])
// outline the card holder view
cardPositionView.backgroundColor = .systemYellow
cardPositionView.layer.borderColor = UIColor.blue.cgColor
cardPositionView.layer.borderWidth = 2
// make the "deck card" gray to represent the deck
deckPileView.backgroundColor = .lightGray
}
func animCard() {
let card = CardView()
card.backgroundColor = colors[colorIDX % colors.count]
colorIDX += 1
card.translatesAutoresizingMaskIntoConstraints = false
card.widthAnchor.constraint(equalToConstant: cardSize.width).isActive = true
card.heightAnchor.constraint(equalToConstant: cardSize.height).isActive = true
view.addSubview(card)
// center the new card on the deckCard
animXAnchor = card.centerXAnchor.constraint(equalTo: deckPileView.centerXAnchor)
animYAnchor = card.centerYAnchor.constraint(equalTo: deckPileView.centerYAnchor)
// activate those constraints
animXAnchor.isActive = true
animYAnchor.isActive = true
// run the animation *after* the card has been placed at its starting position
DispatchQueue.main.async {
// de-activate the current constraints
self.animXAnchor.isActive = false
self.animYAnchor.isActive = false
// center the new card on the cardPositionView
self.animXAnchor = card.centerXAnchor.constraint(equalTo: self.cardPositionView.centerXAnchor)
self.animYAnchor = card.centerYAnchor.constraint(equalTo: self.cardPositionView.centerYAnchor)
// re-activate those constraints
self.animXAnchor.isActive = true
self.animYAnchor.isActive = true
// 1/2 second animation
UIView.animate(withDuration: 0.5, animations: {
self.view.layoutIfNeeded()
})
}
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
animCard()
}
}
It looks like this:
Each time you tap anywhere the code will add a new "card" and animate it from the "deck" view to the "card position" view.

How to Apply Animation while changing UIView size? + Swift 5

Im new to IOS development and I have two buttons in the UIView and when user select the option portrait or landscape, change the UIView re-size and change the background color as well and i need to add animation for that process.
As as ex:
User select portrait and then user can see red color UIVIew. after click the landscape option, animation should be started and it looks like, red color image come front and change the size (changing height and width) for landscape mode and go to previous position and change color to green. i have added small UIView animation on code and it is helpful to you identify the where should we start the animation and finish it. if someone know how to it properly, please, let me know and appreciate your help. please, refer below code
import UIKit
class ViewController: UIViewController {
let portraitWidth : CGFloat = 400
let portraitHeight : CGFloat = 500
let landscapeWidth : CGFloat = 700
let landscapeHeight : CGFloat = 400
var mainView: UIView!
var mainStackView: UIStackView!
let segment: UISegmentedControl = {
let segementControl = UISegmentedControl()
return segementControl
}()
override func viewDidLoad() {
super.viewDidLoad()
self.view.translatesAutoresizingMaskIntoConstraints = false
mainStackView = UIStackView()
mainStackView.axis = .vertical
mainStackView.translatesAutoresizingMaskIntoConstraints = false
mainStackView.alignment = .center
mainStackView.distribution = .equalCentering
self.view.addSubview(mainStackView)
self.segment.insertSegment(withTitle: "Portrait", at: 0, animated: false)
self.segment.insertSegment(withTitle: "Landscape", at: 1, animated: false)
self.segment.selectedSegmentIndex = 0
self.segment.addTarget(self, action: #selector(changeOrientation(_:)), for: .valueChanged)
self.segment.translatesAutoresizingMaskIntoConstraints = false
self.segment.selectedSegmentIndex = 0
mainStackView.addArrangedSubview(self.segment)
let safeAreaLayoutGuide = self.view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
self.mainStackView.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor, constant: 20),
self.mainStackView.centerXAnchor.constraint(equalTo: safeAreaLayoutGuide.centerXAnchor, constant: 0),
])
NSLayoutConstraint.activate([
self.segment.heightAnchor.constraint(equalToConstant: 35),
self.segment.widthAnchor.constraint(equalToConstant: 300)
])
mainView = UIView(frame: .zero)
mainView.translatesAutoresizingMaskIntoConstraints = false
mainStackView.addArrangedSubview(mainView)
mainView.backgroundColor = UIColor.red
NSLayoutConstraint.activate([
mainView.heightAnchor.constraint(equalToConstant: portraitHeight),
mainView.widthAnchor.constraint(equalToConstant: portraitWidth),
mainView.topAnchor.constraint(equalTo: segment.bottomAnchor, constant: 30)
])
}
#IBAction func changeOrientation(_ sender: UISegmentedControl) {
self.mainView.constraints.forEach{ (constraint) in
self.mainView.removeConstraint(constraint)
}
UIView.animate(withDuration: 1.0) {
if (sender.selectedSegmentIndex == 0) {
self.mainView.backgroundColor = UIColor.red
NSLayoutConstraint.activate([
self.mainView.heightAnchor.constraint(equalToConstant: self.portraitHeight),
self.mainView.widthAnchor.constraint(equalToConstant: self.portraitWidth),
self.mainView.topAnchor.constraint(equalTo: self.segment.bottomAnchor, constant: 30)
])
} else {
self.mainView.backgroundColor = UIColor.green
NSLayoutConstraint.activate([
self.mainView.heightAnchor.constraint(equalToConstant: self.landscapeHeight),
self.mainView.widthAnchor.constraint(equalToConstant: self.landscapeWidth),
self.mainView.topAnchor.constraint(equalTo: self.segment.bottomAnchor, constant: 30)
])
}
}
}
}
updated logic
#IBAction func changeOrientation(_ sender: UISegmentedControl) {
UIView.animate(withDuration: 4.0) {
if (sender.selectedSegmentIndex == 0) {
self.mainView.backgroundColor = UIColor.red
self.widthConstraint.constant = self.portraitWidth
self.heightConstraint.constant = self.portraitWidth
} else {
self.mainView.backgroundColor = UIColor.green
self.widthConstraint.constant = self.landscapeWidth
self.heightConstraint.constant = self.landscapeHeight
}
self.mainView.layoutIfNeeded()
} }
It's possible, the basic problem is:
• It's not possible to animate an actual change between one constraint and another.
To change size or shape you simply animate the length of a constraint.
While you are learning I would truly urge you to simply animate the constraints.
So, don't try to "change" constraints, simply animate the length of one or the other.
yourConstraint.constant = 99 // it's that easy
I also truly urge you to just lay it out on storyboard.
You must master that first!
Also there is no reason at all for the stack view, get rid of it for now.
Just have an outlet
#IBOutlet var yourConstraint: NSLayoutConstraint!
and animate
UIView.animateWithDuration(4.0) {
self.yourConstraint.constant = 666
self.view.layoutIfNeeded()
}
Be sure to search in Stack Overflow for "iOS animate constraints" as there is a great deal to learn.
Do this on storyboard, it will take 20 seconds tops for such a simple thing. Once you have mastered storyboard, only then move on to code if there is some reason to do so
Forget the stack view
Simply animate the constant of the constraint(s) to change the size, shape or anything you like.
Be sure to google for the many great articles "animating constraints in iOS" both here on SO and other sites!

adding multiple image views by code

am trying to add image views while looping in an array.. like this:
let v = [1,2,3]
w = self.playgroundimg.frame.width/3 - 48.89/2
h = self.playgroundimg.frame.height/4 - 17
for x in v {
let imageName3 = "lineupcardbg"
let image3 = UIImage(named: imageName3)
let imageView3 = UIImageView(image: image3!)
imageView3.frame = CGRect(x: w, y: h, width: 48.89, height: 70.15)
self.playgroundimg.addSubview(imageView3)
w = w + self.playgroundimg.frame.width/3 - 48.89 - 15
}
this is working fine, but the dimension of the imageview will be different from device to device .. like this:
iphonex:
iphone 7 plus:
as you can see the one in the middle is not centered to the one below it .. how to achieve this so the middle is in the center and the other two are beside it with some space? and be displayed the same in all sizes?
playgroundimg constraints:
Here is a simple example - just to show how to use constraints from code.
You can paste this into a new playground page and run it to see the results.
Change the line:
get { return CGSize(width: 400, height: 400) }
to different sizes to see that the views remain centered.
import PlaygroundSupport
import UIKit
class RoundedImageView: UIImageView {
override init(frame: CGRect) {
super.init(frame: frame)
layer.cornerRadius = 8
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class AlignViewController: UIViewController {
override public var preferredContentSize: CGSize {
get { return CGSize(width: 400, height: 400) }
set { super.preferredContentSize = newValue }
}
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .green
var theImageViews = [UIImageView]()
for _ in 0..<4 {
let imgView = RoundedImageView(frame: CGRect.zero)
imgView.translatesAutoresizingMaskIntoConstraints = false
imgView.backgroundColor = .white
view.addSubview(imgView)
// make all views the same size and width
NSLayoutConstraint.activate([
imgView.widthAnchor.constraint(equalToConstant: 48.89),
imgView.heightAnchor.constraint(equalToConstant: 70.15),
])
theImageViews.append(imgView)
}
// for easy identification of which views we're referencing
let leftView = theImageViews[0]
let centerView = theImageViews[1]
let rightView = theImageViews[2]
let bottomView = theImageViews[3]
NSLayoutConstraint.activate([
// "center view" will be centered horizontally, and a little above centered vertically
centerView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
centerView.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: -25),
// left view is aligned to top of center view, and 20-pts to the left
leftView.topAnchor.constraint(equalTo: centerView.topAnchor),
leftView.trailingAnchor.constraint(equalTo: centerView.leadingAnchor, constant: -20),
// right view is aligned to top of center view, and 20-pts to the right
rightView.topAnchor.constraint(equalTo: centerView.topAnchor),
rightView.leadingAnchor.constraint(equalTo: centerView.trailingAnchor, constant: 20),
// bottom view is centerX aligned to center view, and 20-pts below
bottomView.topAnchor.constraint(equalTo: centerView.bottomAnchor, constant: 20),
bottomView.centerXAnchor.constraint(equalTo: centerView.centerXAnchor),
])
}
}
let viewController = AlignViewController()
PlaygroundPage.current.liveView = viewController
There are many ways to do things in autolayout. One way to solve this is to set the Leading and Trailing autolayout constraints for the playgroundimg view. That would make playgroundimg.frame.width adapt to the device screen width. And make sure that the constraints of the playgroundimg's superview are set.
In Xcode it would look like this for example:
You are using playgroundimg.frame.width in calculating the h and w variables, which I would suggest changing their names to x and y respectively since they denote coordinates and not a width or height:
x = self.playgroundimg.frame.width/3 - 48.89/2
y = self.playgroundimg.frame.height/4 - 17
And update this line too :
x = x + self.playgroundimg.frame.width/3 - 48.89 - 15

UIImageView in UIScrollView does not zoom correctly

I want to allow the user to side pan an image. The image should be scaled to the height of the device and the user is supposed to only be able to scroll left and right. The users is not supposed to be able to zoom.
I have a UIViewController, to which I add a custom subclass ImageScrollView.
This is supposed to display an image in full height, but instead the image is basically displayed un-zoomed. Even though the zoomScale gets calculated correctly, but does not have an effect.
What am I doing wrong?
Thanks
override func viewDidLoad() {
super.viewDidLoad()
let i = UIImage(named: "test.jpg")
let iSV = ImageScrollView(image: i)
self.view.addSubview(iSV)
iSV.fillSuperview()
}
class ImageScrollView: UIScrollView {
let image: UIImage
let imageView = UIImageView()
init(image img: UIImage) {
image = img
imageView.image = image
super.init(frame: .zero)
self.addSubview(imageView)
imageView.fillSuperview()
self.contentSize = image.size
self.showsHorizontalScrollIndicator = false
self.showsVerticalScrollIndicator = false
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
self.zoomScale = getZoomScale()
}
func getZoomScale() -> CGFloat{
let boundSize = self.frame.size
let yScale = boundSize.height / image.size.height
return yScale
}
}
And just for the case it has to do with auto-layout I included the fillSuperview extension.
extension UIView {
public func fillSuperview() {
translatesAutoresizingMaskIntoConstraints = false
if let superview = superview {
topAnchor.constraint(equalTo: superview.topAnchor, constant: 0).isActive = true
leftAnchor.constraint(equalTo: superview.leftAnchor, constant: 0).isActive = true
bottomAnchor.constraint(equalTo: superview.bottomAnchor, constant: 0).isActive = true
rightAnchor.constraint(equalTo: superview.rightAnchor, constant: 0).isActive = true
}
}
}
Since you don't want the image to zoom, I recommend you don't even bother with the zoom controls. A UIImageView knows how to scale its content.
I recommend you do it like this:
Add a constraint that sets the imageView height equal to the scrollView height. This will prevent vertical scrolling.
Add a constraint that sets the imageView width equal to the imageView height with multiplier image.size.width / image.size.height.
Set imageView content mode to .scaleToFill.
To allow you to change the image, keep an aspectRatio property that retains the aspect ratio constraint set in step 2. Set aspectRatio.isActive = false, and then create and activate a new constraint for the new image.
Also, if you might ever have images that aren't wide enough to fill the scrollView horizontally when scaled to fit vertically, consider these changes:
Replace the constraint that sets the imageView width with one that sets the width equal to the imageView height with multiplier max(image.size.width / image.size.height, scrollView.bounds.width / scrollView.bounds.height).
Set imageView content mode to .scaleAspectFit.
Then, when you have a narrow image, the imageView will fill the scrollView, but the .scaleAspectFit will show the entire image centered in the scrollView. This will still work correctly for wide images because the multiplier will match the image aspect ratio and .scaleAspectFit will fill the entire imageView.
You forgot to implement scrollview delegate. And set min & max zoom level for scrollview.
var iSV: ImageScrollView?
let i = UIImage(named: "noWiFi")!
iSV = ImageScrollView(image: i)
if let iSV = iSV {
self.view.addSubview(iSV)
iSV.fillSuperview()
iSV.delegate = self
}
override func viewDidLayoutSubviews() {
if let iSV = iSV {
let scale = iSV.getZoomScale()
iSV.minimumZoomScale = scale
iSV.maximumZoomScale = scale
}
}
extension ViewController: UIScrollViewDelegate {
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return iSV?.imageView
}
}
Note: I just did rough. It may not fulfil your complete requirement

How to get safe area frame of programmatically created ViewController?

Note : Not created ViewController in storyboard
I created ViewController programmatically after giving support for safe guard now my problem is that how i can got his height?
I try following thing but no luck
let guide = view.safeAreaLayoutGuide
let height = guide.layoutFrame.size.height
print("\(height)")
print("\(self.view.frame.height)")
height = 812.0, self.view height = 812.0
Is there any other way?
A safeAreaLayoutGuide, which is iOS 11+, is meant for auto layout and to try to get a frame is close to trying to make orange juice from a lemon.
If what you want is to find out is what you can safely use for a frame, maybe try something like this:
Create a transparent view:
let mySafeView = UIView()
override func viewDidLoad() {
super.viewDidLoad()
mySafeView.backgroundColor = UIColor.clear
mySafeView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(mySafeView)
// if needed, send this view to the back of the hierarchy
view.sendSubview(toBack: mySafeView)
}
Next, in your constraint-building code attach it to the true anchors:
// create generic layout guides
let layoutGuideTop = UILayoutGuide()
view.addLayoutGuide(layoutGuideTop)
let layoutGuideBottom = UILayoutGuide()
view.addLayoutGuide(layoutGuideBottom)
let margins = view.layoutMarginsGuide
view.addLayoutGuide(margins)
// get correct top/bottom margins based on iOS version
if #available(iOS 11, *) {
let guide = view.safeAreaLayoutGuide
layoutGuideTop.topAnchor.constraintEqualToSystemSpacingBelow(guide.topAnchor, multiplier: 1.0).isActive = true
layoutGuideBottom.bottomAnchor.constraintEqualToSystemSpacingBelow(guide.bottomAnchor, multiplier: 1.0).isActive = true
} else {
layoutGuideTop.topAnchor.constraint(equalTo: topLayoutGuide.bottomAnchor).isActive = true
layoutGuideBottom.bottomAnchor.constraint(equalTo: bottomLayoutGuide.topAnchor).isActive = true
}
mySafeView.topAnchor.constraint(equalTo: layoutGuideTop.topAnchor).isActive = true
mySafeView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
mySafeView.bottomAnchor.constraint(equalTo: layoutGuideBottom.bottomAnchor).isActive = true
mySafeView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
Finally, get the frame size:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
print(mySafeView.frame)
}
On an iPhone 6 plus running iOS 11.1 you get:
(0.0, 28.0, 414.0, 716.0)
On an iPhone 6 plus running iOS 9.0 you get:
(0.0, 20.0, 414.0, 716.0)
First off you can't do it in the viewDidLoad. You have to wait till viewDidLayoutSubviews
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
myGlobalVars.sceneRect = view.frame
if #available(iOS 11.0, *) {
myGlobalVars.topSafeArea = view.safeAreaInsets.top
myGlobalVars.bottomSafeArea = view.safeAreaInsets.bottom
} else {
myGlobalVars.topSafeArea = topLayoutGuide.length
myGlobalVars.bottomSafeArea = bottomLayoutGuide.length
}
}

Resources