why NSLayoutConstraint animation works only one time? - swift

in my project I have a someView(of type UIView), that inside holderView(of type UIView).
someView have 2 state.
in state 1 the someView become large and in the step 2 the someView become small.
when some condition where right, someView show in state 1 and when it's not someView show in state 2.
I want to do this with animation and I use NSLayoutConstraint in my project. I'm using this codes(someViewHeight and someViewWidth are of type NSLayoutConstraint):
func minimizeSomeView() {
someViewHeight.constant = holderView.frame.width
someViewWidth.constant = holderView.frame.height
UIView.animate(withDuration: 1.0) {
self.view.layoutIfNeeded()
}
}
func maximizeSomeView() {
someViewHeight.constant = holderView.frame.width/4
someViewWidth.constant = holderView.frame.height/4
UIView.animate(withDuration: 1.0) {
self.view.layoutIfNeeded()
}
}
if someTextField.text != nil {
minimizeSomeView()
} else. {
maximizeSomeView()
}
I define someViewHeight and someViewWidth inside viewDidLayoutSubviews(), this is my codes:
class ViewController: UIViewController {
private var holderView, someView: UIView!
private var someViewHeight,someViewWidth: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
super.viewDidLoad()
view.backgroundColor = .white
// Holder View
holderView = UIView()
holderView.backgroundColor = .red
holderView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(holderView)
NSLayoutConstraint.activate([
holderView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
holderView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
holderView.topAnchor.constraint(equalTo: view.topAnchor, constant: 120),
holderView.heightAnchor.constraint(equalToConstant: view.frame.height * 40 / 100)
])
// Some View
someView = UIView()
someView.backgroundColor = .blue
someView.translatesAutoresizingMaskIntoConstraints = false
holderView.addSubview(someView)
someView.topAnchor.constraint(equalTo: holderView.topAnchor).isActive = true
someView.trailingAnchor.constraint(equalTo: holderView.trailingAnchor).isActive = true
// Some TextField
let someTextField = UITextField()
someTextField.backgroundColor = .yellow
someTextField.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(someTextField)
NSLayoutConstraint.activate([
someTextField.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
someTextField.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
someTextField.topAnchor.constraint(equalTo: view.bottomAnchor, constant: -100),
someTextField.heightAnchor.constraint(equalToConstant: 50)
])
someTextField.addTarget(self, action: #selector(textFieldDidChange(_:)), for: .editingChanged)
}
override func viewDidLayoutSubviews() {
someViewHeight = someView.heightAnchor.constraint(equalToConstant: holderView.frame.width)
someViewHeight.isActive = true
someViewWidth = someView.widthAnchor.constraint(equalToConstant: holderView.frame.width)
someViewWidth.isActive = true
}
func minimizeSomeView() {
someViewHeight.constant = holderView.frame.width/4
someViewWidth.constant = holderView.frame.height/4
UIView.animate(withDuration: 1.0) {
self.view.layoutIfNeeded()
}
}
func maximizeSomeView() {
someViewHeight.constant = holderView.frame.width
someViewWidth.constant = holderView.frame.height
UIView.animate(withDuration: 1.0) {
self.view.layoutIfNeeded()
}
}
#objc func textFieldDidChange(_ textfield: UITextField) {
if textfield.text!.count > 0 {
self.minimizeSomeView()
} else {
self.maximizeSomeView()
}
}
}

You should not update the constraints in viewDidLayoutSubviews, specifically when you are using auto layout. You don't need this block
When you add constraint you can initialize and make them active.
Here we can specify the constraint in proportion. i.e Height of someView is 0.4 of the holderView. Similarly, Width of someView is 0.4 of the holderView.
To fix the issue , you can perform below changes
// Your some View Constraints
someView.topAnchor.constraint(equalTo: holderView.topAnchor).isActive = true
someView.trailingAnchor.constraint(equalTo: holderView.trailingAnchor).isActive = true
maximizeSomeView()
Remove the viewDidLayouSubView Code
Update your maximize and minimize events. Here I have to remove the existing constraints as .multiplier is a readonly property.
func minimizeSomeView() {
removeExistingConstriant()
UIView.animate(withDuration: 1.0) { [unowned self] in
self.someViewWidth = self.someView.widthAnchor.constraint(equalTo: self.holderView.widthAnchor, multiplier: 1.0)
self.someViewWidth.isActive = true
self.someViewHeight = self.someView.heightAnchor.constraint(equalTo:
self.holderView.heightAnchor, multiplier: 1.0)
self.someViewHeight.isActive = true
self.view.layoutIfNeeded()
}
}
func maximizeSomeView() {
removeExistingConstriant()
UIView.animate(withDuration: 1.0) { [unowned self] in
self.someViewWidth = self.someView.widthAnchor.constraint(equalTo: self.holderView.widthAnchor, multiplier: 0.4)
self.someViewWidth.isActive = true
self.someViewHeight = self.someView.heightAnchor.constraint(equalTo:
self.holderView.heightAnchor, multiplier: 0.4)
self.someViewHeight.isActive = true
self.view.layoutIfNeeded()
}
}
func removeExistingConstriant(){
if self.someViewHeight != nil {
self.someViewHeight.isActive = false
}
if self.someViewWidth != nil {
self.someViewWidth.isActive = false
}
}

Related

Want to flip two views so that one view hides and other shows swift

I want to flip two views from top or left, direction is not a big deal
Basicaly i want to flip one view so that in animation one view hides or and other shows like card flip
I want this but with two view
https://www.youtube.com/watch?v=4kSLbuB-MlU
I found it in objective C (maybe)
link is attached
how to flip two views at once?
Image is also attached
I have tried something below but could not find a suitable solution
UIView.transition(from: vu1, to: vu2, duration: 1, options: .transitionFlipFromBottom, completion: { _ in
})
Also tried this but did not help
func flipTransition (with view1: UIView, view2: UIView, isReverse: Bool = true) {
var transitionOptions = UIView.AnimationOptions()
transitionOptions = isReverse ? [.transitionFlipFromLeft] : [.transitionFlipFromRight]
UIView.transition(with: view1, duration: 1.5, options: transitionOptions, animations: {
view1.isHidden = true
})
UIView.transition(with: view2, duration: 1.5, options: transitionOptions, animations: {
view2.isHidden = false
})
}
UIView.transition(with applies to a container view so you can try putting your subviews in a container view and apply UIView.transition on container view. Because you havent added any code am assuming few things here to answer
class ViewController: UIViewController {
#IBOutlet weak var button: UIButton!
let view1 = UIView()
let view2 = UIView()
let containerView = UIView()
override func viewDidLoad() {
super.viewDidLoad()
view1.backgroundColor = UIColor.red
view2.backgroundColor = UIColor.green
view.addSubview(containerView)
containerView.addSubview(view1)
containerView.addSubview(view2)
containerView.translatesAutoresizingMaskIntoConstraints = false
view1.translatesAutoresizingMaskIntoConstraints = false
view2.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
containerView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
containerView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
containerView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
containerView.topAnchor.constraint(equalTo: view.topAnchor, constant: 100),
view1.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
view1.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
view1.bottomAnchor.constraint(equalTo: containerView.bottomAnchor),
view1.topAnchor.constraint(equalTo: containerView.topAnchor, constant: 100),
view2.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
view2.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
view2.bottomAnchor.constraint(equalTo: containerView.bottomAnchor),
view2.topAnchor.constraint(equalTo: containerView.topAnchor, constant: 100)
])
view1.isHidden = false
view2.isHidden = true
}
#IBAction func changeTapped() {
UIView.transition(with: containerView,
duration: 1.0,
options: .transitionFlipFromBottom, animations: {[weak self] in
guard let self = self else { return }
self.view1.isHidden = !self.view1.isHidden
self.view2.isHidden = !self.view2.isHidden
})
}
O/P:

Increase UIView Height Programmatically Based on Changing SubView Height

I have been unable to figure out how to solve this issue of mine. I tried to follow the answer from this post. How to change UIView height based on elements inside it
Like the post answer says to do, I have:
set autolayout constraints between UIContainerView top to the UITextView top and UIContainerView bottom to the UITextView bottom (#1)
set height constraint on the text view (#2) and change its constant when resizing the text view (#3)
I have to do this all programmatically. I first set the frame for the container view and give it a specified height. I'm not sure if that is okay too. I also add (#1) in viewDidLoad and am unsure if that's correct.
The text view is not able to increase height either with the current constraints (it is able to if I remove the topAnchor constraint but the container view still doesn't change size).
class ChatController: UICollectionViewController, UICollectionViewDelegateFlowLayout, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
lazy var containerView: UIView = {
let containerView = UIView()
containerView.translatesAutoresizingMaskIntoConstraints = false
containerView.frame = CGRect(x: 0, y: 0, width: self.view.frame.width, height: self.view.frame.height * 0.075)
return containerView
}()
lazy var textView: UITextView = {
let textView = UITextView()
textView.text = "Enter message..."
textView.isScrollEnabled = false
textView.translatesAutoresizingMaskIntoConstraints = false
textView.delegate = self
return textView
}()
override func viewDidLoad() {
super.viewDidLoad()
...
textViewDidChange(self.textView)
addContainerSubViews()
(#1)
containerView.topAnchor.constraint(equalTo: self.textView.topAnchor, constant: -UIScreen.main.bounds.size.height * 0.075 * 0.2).isActive = true
containerView.bottomAnchor.constraint(equalTo: self.textView.bottomAnchor, constant: UIScreen.main.bounds.size.height * 0.075 * 0.2).isActive = true
}
func addContainerSubViews() {
let height = UIScreen.main.bounds.size.height
let width = UIScreen.main.bounds.size.width
let containerHeight = height * 0.075
...//constraints for imageView and sendButton...
containerView.addSubview(self.textView)
self.textView.leftAnchor.constraint(equalTo: imageView.rightAnchor, constant: width/20).isActive = true
self.textView.rightAnchor.constraint(equalTo: sendButton.leftAnchor, constant: -width/20).isActive = true
(#2)
self.textView.heightAnchor.constraint(equalToConstant: containerHeight * 0.6).isActive = true
}
override var inputAccessoryView: UIView? {
get {
return containerView
}
}
(#3)
func textViewDidChange(_ textView: UITextView) {
let size = CGSize(width: view.frame.width, height: .infinity)
let estimatedSize = textView.sizeThatFits(size)
textView.constraints.forEach { (constraint) in
if constraint.firstAttribute == .height {
constraint.constant = estimatedSize.height
}
}
}
You can do this all with auto-layout / constraints. Because a UITextView with scrolling disabled will "auto-size" its height based on the text, no need to calculate height and change constraint constant.
Here's an example -- it's from a previous answer, modified to include your image view and send button:
class ViewController: UIViewController {
let testLabel: InputLabel = InputLabel()
override func viewDidLoad() {
super.viewDidLoad()
let instructionLabel = UILabel()
instructionLabel.textAlignment = .center
instructionLabel.text = "Tap yellow label to edit..."
let centeringFrameView = UIView()
// label properties
let fnt: UIFont = .systemFont(ofSize: 32.0)
testLabel.isUserInteractionEnabled = true
testLabel.font = fnt
testLabel.adjustsFontSizeToFitWidth = true
testLabel.minimumScaleFactor = 0.25
testLabel.numberOfLines = 2
testLabel.setContentHuggingPriority(.required, for: .vertical)
let minLabelHeight = ceil(fnt.lineHeight)
// so we can see the frames
centeringFrameView.backgroundColor = .red
testLabel.backgroundColor = .yellow
[centeringFrameView, instructionLabel, testLabel].forEach {
$0.translatesAutoresizingMaskIntoConstraints = false
}
view.addSubview(instructionLabel)
view.addSubview(centeringFrameView)
centeringFrameView.addSubview(testLabel)
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
// instruction label centered at top
instructionLabel.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
instructionLabel.centerXAnchor.constraint(equalTo: g.centerXAnchor),
// centeringFrameView 20-pts from instructionLabel bottom
centeringFrameView.topAnchor.constraint(equalTo: instructionLabel.bottomAnchor, constant: 20.0),
// Leading / Trailing with 20-pts "padding"
centeringFrameView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
centeringFrameView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
// test label centered vertically in centeringFrameView
testLabel.centerYAnchor.constraint(equalTo: centeringFrameView.centerYAnchor, constant: 0.0),
// Leading / Trailing with 20-pts "padding"
testLabel.leadingAnchor.constraint(equalTo: centeringFrameView.leadingAnchor, constant: 20.0),
testLabel.trailingAnchor.constraint(equalTo: centeringFrameView.trailingAnchor, constant: -20.0),
// height will be zero if label has no text,
// so give it a min height of one line
testLabel.heightAnchor.constraint(greaterThanOrEqualToConstant: minLabelHeight),
// centeringFrameView height = 3 * minLabelHeight
centeringFrameView.heightAnchor.constraint(equalToConstant: minLabelHeight * 3.0)
])
// to handle user input
testLabel.editCallBack = { [weak self] str in
guard let self = self else { return }
self.testLabel.text = str
}
testLabel.doneCallBack = { [weak self] in
guard let self = self else { return }
// do something when user taps done / enter
}
let t = UITapGestureRecognizer(target: self, action: #selector(self.labelTapped(_:)))
testLabel.addGestureRecognizer(t)
}
#objc func labelTapped(_ g: UITapGestureRecognizer) -> Void {
testLabel.becomeFirstResponder()
testLabel.inputContainerView.theTextView.text = testLabel.text
testLabel.inputContainerView.theTextView.becomeFirstResponder()
}
}
class InputLabel: UILabel {
var editCallBack: ((String) -> ())?
var doneCallBack: (() -> ())?
override var canBecomeFirstResponder: Bool {
return true
}
override var canResignFirstResponder: Bool {
return true
}
override var inputAccessoryView: UIView? {
get { return inputContainerView }
}
lazy var inputContainerView: CustomInputAccessoryView = {
let v = CustomInputAccessoryView()
v.editCallBack = { [weak self] str in
guard let self = self else { return }
self.editCallBack?(str)
}
v.doneCallBack = { [weak self] in
guard let self = self else { return }
self.resignFirstResponder()
}
return v
}()
}
class CustomInputAccessoryView: UIView, UITextViewDelegate {
var editCallBack: ((String) -> ())?
var doneCallBack: (() -> ())?
let theTextView: UITextView = {
let tv = UITextView()
tv.isScrollEnabled = false
tv.font = .systemFont(ofSize: 16)
tv.autocorrectionType = .no
tv.returnKeyType = .done
return tv
}()
let imgView: UIImageView = {
let v = UIImageView()
v.contentMode = .scaleAspectFit
v.clipsToBounds = true
return v
}()
let sendButton: UIButton = {
let v = UIButton()
return v
}()
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .lightGray
autoresizingMask = [.flexibleHeight, .flexibleWidth]
if let img = UIImage(named: "testImage") {
imgView.image = img
} else {
imgView.backgroundColor = .systemBlue
}
let largeConfig = UIImage.SymbolConfiguration(pointSize: 22, weight: .regular, scale: .large)
let buttonImg = UIImage(systemName: "paperplane.fill", withConfiguration: largeConfig)
sendButton.setImage(buttonImg, for: .normal)
[theTextView, imgView, sendButton].forEach { v in
addSubview(v)
v.translatesAutoresizingMaskIntoConstraints = false
}
// if we want to see the image view and button frames
//[imgView, sendButton].forEach { v in
// v.backgroundColor = .systemYellow
//}
NSLayoutConstraint.activate([
// constrain image view 40x40 with 8-pts leading
imgView.widthAnchor.constraint(equalToConstant: 40.0),
imgView.heightAnchor.constraint(equalTo: imgView.widthAnchor),
imgView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 8.0),
// constrain image view 40x40 with 8-pts trailing
sendButton.widthAnchor.constraint(equalToConstant: 40.0),
sendButton.heightAnchor.constraint(equalTo: sendButton.widthAnchor),
sendButton.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -8.0),
// constrain text view with 10-pts from
// image view trailing
// send button leading
theTextView.leadingAnchor.constraint(equalTo: imgView.trailingAnchor, constant: 10),
theTextView.trailingAnchor.constraint(equalTo: sendButton.leadingAnchor, constant: -10),
// constrain image view and button
// centered vertically
// at least 8-pts top and bottom
imgView.centerYAnchor.constraint(equalTo: centerYAnchor),
sendButton.centerYAnchor.constraint(equalTo: centerYAnchor),
imgView.topAnchor.constraint(greaterThanOrEqualTo: topAnchor, constant: 8.0),
sendButton.topAnchor.constraint(greaterThanOrEqualTo: topAnchor, constant: 8.0),
imgView.bottomAnchor.constraint(lessThanOrEqualTo: bottomAnchor, constant: -8.0),
sendButton.bottomAnchor.constraint(lessThanOrEqualTo: bottomAnchor, constant: -8.0),
// constrain text view 8-pts top/bottom
theTextView.topAnchor.constraint(equalTo: topAnchor, constant: 8.0),
theTextView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -8.0),
])
theTextView.delegate = self
}
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
if (text == "\n") {
textView.resignFirstResponder()
doneCallBack?()
}
return true
}
func textViewDidChange(_ textView: UITextView) {
editCallBack?(textView.text ?? "")
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override var intrinsicContentSize: CGSize {
return .zero
}
}
Output:

UIScrollView animate offset change when removing nested view

I have a UIScrollView that contains a stack view - I'm basically replicating a tabs feature.
One tab has a taller view than the other, so when I hide the view in the stack view it resizes.
This causes the scroll view to jump to the offset that fits the shorter view, in the event the user has scrolled to the top.
Is it possible to instead animate this change? Instead of the jump, the view scrolls to the correct offset? I am unsure how to achieve this.
final class ScrollViewController: UIViewController {
private var visibleTab: TabState = .overview {
didSet {
guard oldValue != visibleTab else { return }
switch visibleTab {
case .overview:
self.spacesTab.isHidden = true
self.overviewTab.isHidden = false
case .spaces:
self.spacesTab.isHidden = false
self.overviewTab.isHidden = true
}
}
}
enum TabState {
case overview
case spaces
}
private lazy var scrollView: UIScrollView = {
let view = UIScrollView(frame: .zero)
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = .white
view.delegate = self
view.alwaysBounceVertical = true
return view
}()
private let contentStackView: UIStackView = {
let view = UIStackView(frame: .zero)
view.translatesAutoresizingMaskIntoConstraints = false
view.axis = .vertical
view.alignment = .fill
view.spacing = 8
view.distribution = .fill
return view
}()
private let tabSelectorView: UIStackView = {
let view = UIStackView(frame: .zero)
view.axis = .horizontal
view.distribution = .fillEqually
return view
}()
private let overviewTab: UIView = {
let view = UIView(frame: .zero)
view.backgroundColor = .darkGray
view.heightAnchor.constraint(equalToConstant: 100).isActive = true
view.isHidden = false
return view
}()
private let spacesTab: UIView = {
let view = UIView(frame: .zero)
view.backgroundColor = .lightGray
view.heightAnchor.constraint(equalToConstant: 780).isActive = true
view.isHidden = true
return view
}()
private let profileHeader = ScrollViewProfileHeaderView(frame: .zero)
private lazy var overviewTabButton = makeButton(title: "Overview")
private lazy var spacesTabButton = makeButton(title: "Spaces")
override func viewDidLoad() {
super.viewDidLoad()
configureUI()
}
}
extension ScrollViewController: UIScrollViewDelegate { }
private extension ScrollViewController {
func configureUI() {
overviewTabButton.addTarget(self, action: #selector(showOverviewTab), for: .touchUpInside)
spacesTabButton.addTarget(self, action: #selector(showSpacesTab), for: .touchUpInside)
[overviewTabButton, spacesTabButton].forEach(tabSelectorView.addArrangedSubview)
profileHeader.translatesAutoresizingMaskIntoConstraints = false
tabSelectorView.translatesAutoresizingMaskIntoConstraints = false
[overviewTab, spacesTab].forEach(contentStackView.addArrangedSubview)
[profileHeader, tabSelectorView, contentStackView].forEach(scrollView.addSubview(_:))
view.addSubview(scrollView)
NSLayoutConstraint.activate([
scrollView.topAnchor.constraint(equalTo: view.topAnchor),
scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
profileHeader.topAnchor.constraint(equalTo: scrollView.topAnchor),
profileHeader.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor),
profileHeader.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor),
tabSelectorView.topAnchor.constraint(equalTo: profileHeader.bottomAnchor),
tabSelectorView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor),
tabSelectorView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor),
contentStackView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor),
contentStackView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor),
contentStackView.topAnchor.constraint(equalTo: tabSelectorView.bottomAnchor, constant: 8),
contentStackView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor),
contentStackView.widthAnchor.constraint(equalTo: scrollView.widthAnchor)
])
}
func makeButton(title: String) -> UIButton {
let button = UIButton(type: .system)
button.setTitle(title, for: .normal)
button.backgroundColor = .lightGray
return button
}
#objc func showOverviewTab() {
visibleTab = .overview
}
#objc func showSpacesTab() {
visibleTab = .spaces
}
}
final class ScrollViewProfileHeaderView: UIView {
private let headerImage: UIImageView = {
let view = UIImageView(frame: .zero)
view.translatesAutoresizingMaskIntoConstraints = false
view.contentMode = .scaleAspectFill
view.clipsToBounds = true
view.backgroundColor = .systemTeal
return view
}()
private let profileCard: ProfileCardView = {
let view = ProfileCardView(frame: .zero)
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = .purple
return view
}()
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .white
[headerImage, profileCard].forEach(addSubview(_:))
NSLayoutConstraint.activate([
headerImage.topAnchor.constraint(equalTo: topAnchor),
headerImage.leadingAnchor.constraint(equalTo: leadingAnchor),
headerImage.trailingAnchor.constraint(equalTo: trailingAnchor),
headerImage.heightAnchor.constraint(equalToConstant: 180),
profileCard.topAnchor.constraint(equalTo: headerImage.centerYAnchor),
profileCard.leadingAnchor.constraint(equalTo: safeAreaLayoutGuide.leadingAnchor, constant: 48),
profileCard.bottomAnchor.constraint(equalTo: safeAreaLayoutGuide.bottomAnchor, constant: -32),
profileCard.trailingAnchor.constraint(equalTo: safeAreaLayoutGuide.trailingAnchor, constant: -48),
profileCard.heightAnchor.constraint(equalToConstant: 270),
])
}
required init?(coder: NSCoder) {
return nil
}
}
You will probably want to make some additional changes, but this might get you on your way.
In your visibleTab / didSet block, use UIView.animate() when you hide the spacesTab:
private var visibleTab: TabState = .overview {
didSet {
guard oldValue != visibleTab else { return }
switch self.visibleTab {
case .overview:
// set duration longer, such as 1.0, to clearly see the animation...
UIView.animate(withDuration: 0.3) {
self.spacesTab.isHidden = true
self.overviewTab.isHidden = false
}
case .spaces:
self.spacesTab.isHidden = false
self.overviewTab.isHidden = true
}
}
}

UIView Animation just work one time than it doesn't work

I try to do an animation for my view but if just work in first time which is isOpen = true it work but when I call my function again isOpen = false nothing changes?
Perent view is self (UIView). Child is label(UILabel).
private func expansionView(isOpen: Bool) {
if isOpen {
label.backgroundColor = .white
NSLayoutConstraint.activate([
label.centerYAnchor.constraint(equalTo: self.topAnchor),
label.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 15),
])
UIView.animate(withDuration: 1, animations: {
self.layoutIfNeeded()
}) { (_) in
}
} else {
label.backgroundColor = .clear
NSLayoutConstraint.activate([
label.centerYAnchor.constraint(equalTo: self.centerYAnchor),
label.leadingAnchor.constraint(equalTo: self.leadingAnchor , constant: 15),
])
UIView.animate(withDuration: 1, animations: {
self.layoutIfNeeded()
}) { (_) in
print("Animation Completed!!!")
}
}
}
You need to have two constraints to make one of them active.
You can also animate changing label's background color to clear. And you can simplify your function like below.
// define both vertical constraints
var constraintToCenterYAnchor: NSLayoutConstraint!
var constraintToTopAnchor: NSLayoutConstraint!
// where you init your view..
init() {
// init your constraints
constraintToTopAnchor = label.centerYAnchor.constraint(equalTo: topAnchor)
constraintToCenterYAnchor = label.centerYAnchor.constraint(equalTo: centerYAnchor)
// set and activate other constraints once.
label.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 15).isActive = true
// update background color of the label
label.backgroundColor = .clear
label.layer.backgroundColor = UIColor.white.cgColor
}
// simplify your function
private func expansionView(_ isOpen: Bool) {
constraintToTopAnchor.isActive = isOpen
constraintToCenterYAnchor.isActive = !isOpen
UIView.animate(withDuration: 1) {
self.label.layer.opacity = isOpen ? 1.0 : 0.0
self.layoutIfNeeded()
}
}
You need to have two contraints to activate and deactivate ...
lazy var centerXConstraint = label.centerYAnchor.constraint(equalTo: self.centerYAnchor)
lazy var topConstraint = label.leadingAnchor.constraint(equalTo: self.leadingAnchor , constant: 15)
init() {
label.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 15).isActive = true
}
private func expansionView(isOpen: Bool) {
if isOpen {
label.backgroundColor = .white
centerXConstraint.isActive = false
topConstraint.isActive = true
UIView.animate(withDuration: 1, animations: {
self.layoutIfNeeded()
}) { (_) in
}
} else {
label.backgroundColor = .clear
centerXConstraint.isActive = true
topConstraint.isActive = false
UIView.animate(withDuration: 1, animations: {
self.layoutIfNeeded()
}) { (_) in
print("Animation Completed!!!")
}
}
}

.tag on slider not changing the alpha of imagview

My swift code below has 2 different buttons which should effect the slider baised on the .tagnumber they initialize. B1 should effect the alhpa or transpency of the imageivew and b2 should decrease / increase the size. What b2 does works. What B1 does not work.My code does not use any storyboards. Also the uislider should only do one task it cannont resize the imageview and change the alpha at the same time. Only 1 task.
import UIKit
class ViewController: UIViewController {
var pzc = UIImageView()
var s = UISlider()
var b1 = UIButton()
var b2 = UIButton()
var jessicaAlba:Float = 50
var topConstraint: NSLayoutConstraint!
var heightConstraint: NSLayoutConstraint!
var leadingConstraint: NSLayoutConstraint!
var trailingConstraint: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
[pzc,s,b1,b2].forEach {
$0.translatesAutoresizingMaskIntoConstraints = false
view.addSubview($0)
}
[b1,b2].forEach {
$0.backgroundColor = .systemRed
}
pzc.backgroundColor = .systemGray
b1.frame = CGRect(x: view.center.x-115, y: view.center.y+200, width: 30, height: 30)
b2.frame = CGRect(x: view.center.x-115, y: view.center.y+250, width: 30, height: 30)
s.addTarget(self, action: #selector(moveRight), for: .touchUpInside)
b1.addTarget(self, action: #selector(mr1), for: .touchUpInside)
b2.addTarget(self, action: #selector(mr2), for: .touchUpInside)
NSLayoutConstraint.activate ([
b1.trailingAnchor.constraint(equalTo: view.centerXAnchor, constant :37.5),
b1.topAnchor.constraint(equalTo: view.centerYAnchor, constant : 225),
b1.widthAnchor.constraint(equalToConstant: 75),
b1.heightAnchor.constraint(equalToConstant: 50),
b2.trailingAnchor.constraint(equalTo: view.centerXAnchor, constant :130),
b2.topAnchor.constraint(equalTo: view.centerYAnchor, constant : 225),
b2.widthAnchor.constraint(equalToConstant: 75),
b2.heightAnchor.constraint(equalToConstant: 50),
])
s.minimumValue = 50
s.maximumValue = 200
s.setValue(jessicaAlba, animated: false)
view.addSubview(s)
s.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 10).isActive = true
s.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -10).isActive = true
s.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -10).isActive = true
pzc.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(pzc)
topConstraint = pzc.topAnchor.constraint(equalTo: view.topAnchor, constant: CGFloat(jessicaAlba))
topConstraint.isActive = true
heightConstraint = pzc.heightAnchor.constraint(equalTo: view.heightAnchor , multiplier: 0.5, constant: CGFloat(-jessicaAlba))
heightConstraint.isActive = true
leadingConstraint = pzc.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: CGFloat(jessicaAlba))
leadingConstraint.isActive = true
trailingConstraint = pzc.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: CGFloat(-jessicaAlba))
trailingConstraint.isActive = true
}
#objc func moveRight() {
if s.tag == 1 {
pzc.alpha = CGFloat(s.value)
}
if s.tag == 2 {
changeSize()
}
}
#objc func changeSize() {
UIView.animate(withDuration: 0.5, animations: {
self.jessicaAlba = self.s.value
self.topConstraint.constant = CGFloat(self.jessicaAlba)
self.heightConstraint.constant = CGFloat(-self.jessicaAlba)
self.leadingConstraint.constant = CGFloat(self.jessicaAlba)
self.trailingConstraint.constant = CGFloat(-self.jessicaAlba)
self.view.layoutIfNeeded()
}) { (finished) in
}
}
#objc func mr1() {
b1.backgroundColor = .brown
b2.backgroundColor = .systemPink
s.tag = 1
}
#objc func mr2() {
b2.backgroundColor = .brown
b1.backgroundColor = .systemPink
s.tag = 2
}
}
Here multiple target are added on your UISlider. Just modify your function.
#objc func moveRight() {
if s.tag == 1 {
let diff = s.maximumValue-s.minimumValue
pzc.alpha = CGFloat(s.value/diff)
}
if s.tag == 2 {
changeSize()
}
}
You can also modify your changeSize() function for animation.
#objc func changeSize() {
UIView.animate(withDuration: 0.5, animations: {
self.jessicaAlba = self.s.value
self.topConstraint.constant = CGFloat(self.jessicaAlba)
self.heightConstraint.constant = CGFloat(-self.jessicaAlba)
self.leadingConstraint.constant = CGFloat(self.jessicaAlba)
self.trailingConstraint.constant = CGFloat(-self.jessicaAlba)
self.view.layoutIfNeeded()
}) { (finished) in
}
}