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.
Related
I've created a custom UIView with a subview containing several square subviews which are sized using layout constraints. I'd like for each subview to be circular so I'm holding a reference to the view in an array, then iterating through the array of views in the layoutSubviews method and applying a corner radius that is half the view.bounds.height.
The problem is, this only works 70% of the time. Sometimes the view doesn't seem to know its bounds and the views are rendered as square.
I'm probably approaching this all wrong... does anyone have any advice for me? Where is it safe to apply the corner radius so that it always renders as a perfect circle?
Thanks
The most reliable way to manage this is to subclass UIView and let it handle its own rounding.
For example:
class RoundView: UIView {
override func layoutSubviews() {
super.layoutSubviews()
// this assumes constraints keep
// self at a 1:1 ratio (square)
layer.cornerRadius = bounds.height * 0.5
}
}
This simple controller example:
class RoundDemoVC: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let colors: [UIColor] = [
.systemRed, .systemGreen, .systemBlue
]
let widths: [CGFloat] = [
200, 140, 140
]
let xPos: [CGFloat] = [
20, 120, 180
]
let g = view.safeAreaLayoutGuide
for i in 0..<colors.count {
let v = RoundView()
v.backgroundColor = colors[i]
view.addSubview(v)
v.translatesAutoresizingMaskIntoConstraints = false
v.widthAnchor.constraint(equalToConstant: widths[i]).isActive = true
// make it square
v.heightAnchor.constraint(equalTo: v.widthAnchor).isActive = true
v.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: xPos[i]).isActive = true
v.centerYAnchor.constraint(equalTo: g.centerYAnchor).isActive = true
}
}
}
Produces this output:
No need to iterate through subviews and setting values based on frame sizes... all you need to do is set the size/position constraints.
Donmag's example is cool.Also, you can do it this way without writing code to layoutsubview. You must add this settings after adding the constraints of the subviews in for iteration.
.......
....... // constraint settings
subView.layoutIfNeeded()
subView.layer.cornerRadius = subView.frame.size.height/2
You can apply the corner radius inside the layoutSubviews() method
override func layoutSubviews() {
super.layoutSubviews()
// This is the place to apply corner radius
}
I am using snap kit to set out constraints. The first image is what I'm trying to achieve with the code below. How can I set the constraints off the circle's width and height to be dynamic on any iPhone screen ?
profileImage = UIImageView()
profileImage.layer.borderWidth = 2
profileImage.layer.borderColor = UIColor.lightBlue.cgColor
profileImage.layer.cornerRadius = 130
profileImage.clipsToBounds = true
profileImage.layer.masksToBounds = true
let tapGesture = UITapGestureRecognizer(target: self, action:#selector((tappedImage)))
profileImage.addGestureRecognizer(tapGesture)
profileImage.isUserInteractionEnabled = true
profileImage.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(profileImage)
profileImage.snp.makeConstraints { (make) in
make.centerX.equalToSuperview()
make.top.equalTo(titleLabel.snp.bottom).offset(topMargin*2)
make.width.height.equalTo(view.snp.width).multipliedBy(0.71)
}
enter image description here
enter image description here
Couple points...
First, 71% of the view width will probably be too big. Start around 50% and adjust to your liking.
You are using .cornerRadius = 130 but your imageView may not be that size (certainly not on different devices), so you want to set the corner radius to one-half the width of the image view (or height, doesn't matter since it will be a square 1:1 ratio).
You could wait until viewDidLayoutSubviews() to find out the run-time size of the image view, but if your image view ends up as a subview of another view, it won't be set at that point either.
Much easier to create a very simple UIImageView subclass:
class ProfileImageView: UIImageView {
override func layoutSubviews() {
super.layoutSubviews()
layer.borderWidth = 2
layer.borderColor = UIColor.systemBlue.cgColor
layer.cornerRadius = bounds.width * 0.5
clipsToBounds = true
layer.masksToBounds = true
}
}
Then your view controller looks like this:
class ViewController: UIViewController {
var profileImage: ProfileImageView!
// your top margin value
let topMargin: CGFloat = 20
override func viewDidLoad() {
super.viewDidLoad()
profileImage = ProfileImageView()
if let img = UIImage(named: "sampleProfilePic") {
profileImage.image = img
}
profileImage.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(profileImage)
// respect safe-area
let g = view.safeAreaLayoutGuide
// this should give the same layout as your "snap" constraints
NSLayoutConstraint.activate([
// center horizontally
profileImage.centerXAnchor.constraint(equalTo: g.centerXAnchor),
// your topMargin value * 2 from safe-area top
profileImage.topAnchor.constraint(equalTo: g.topAnchor, constant: topMargin * 2.0),
// width
// 71% of width of safe-area is probably too wide
// try starting at 50%
profileImage.widthAnchor.constraint(equalTo: g.widthAnchor, multiplier: 0.50),
// 1:1 ratio (square)
profileImage.heightAnchor.constraint(equalTo: profileImage.widthAnchor),
])
// profileImage.snp.makeConstraints { (make) in
// make.centerX.equalToSuperview()
// make.top.equalTo(titleLabel.snp.bottom).offset(topMargin*2)
// make.width.height.equalTo(view.snp.width).multipliedBy(0.71)
// }
let tapGesture = UITapGestureRecognizer(target: self, action:#selector((tappedImage)))
profileImage.addGestureRecognizer(tapGesture)
profileImage.isUserInteractionEnabled = true
}
}
Whenever the size of your profileImage view changes, the ProfileImageView class' layoutSubviews() will automatically set the corner radius appropriately.
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)
])
}
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!
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