I have a UICollectionView
inside an inputAccessoryView
for selecting images while creating a post (similar to Twitter).
When the user starts typing I want to animate the UICollectionView
down with a UIView
animation function.
Preferred Outcome
Code
func animateCollectionView() {
UIView.animate(withDuration: 1, delay: 0, options: .showHideTransitionViews) {
self.collectionView.transform = .init(scaleX: 0, y: 100)
} completion: { finished in
if finished {
print("ANIMATION COMPLETED")
}
}
}
With this, the UICollectionView
gets removed immediately and the console is printing after 1 second (as expected). However, the animation is not happening.
Constraints
NSLayoutConstraint.activate([
uploadVoiceNoteButton.heightAnchor.constraint(equalToConstant: 48),
uploadMediaButton.heightAnchor.constraint(equalToConstant: 48),
uploadPollButton.heightAnchor.constraint(equalToConstant: 48),
characterCountView.heightAnchor.constraint(equalToConstant: 48),
characterCountView.widthAnchor.constraint(equalToConstant: 18),
hStackView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 16),
hStackView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -16),
hStackView.bottomAnchor.constraint(equalTo: bottomAnchor),
separator.heightAnchor.constraint(equalToConstant: Height.separator),
separator.leadingAnchor.constraint(equalTo: leadingAnchor),
separator.trailingAnchor.constraint(equalTo: trailingAnchor),
separator.topAnchor.constraint(equalTo: hStackView.topAnchor),
replyAllowanceButton.heightAnchor.constraint(equalToConstant: 51),
replyAllowanceButton.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 16),
replyAllowanceButton.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -16),
replyAllowanceButton.bottomAnchor.constraint(equalTo: separator.topAnchor),
collectionViewSeparator.bottomAnchor.constraint(equalTo: replyAllowanceButton.topAnchor),
collectionViewSeparator.leadingAnchor.constraint(equalTo: leadingAnchor),
collectionViewSeparator.trailingAnchor.constraint(equalTo: trailingAnchor),
collectionViewSeparator.heightAnchor.constraint(equalToConstant: Height.separator),
collectionView.topAnchor.constraint(equalTo: topAnchor),
collectionView.leadingAnchor.constraint(equalTo: leadingAnchor),
collectionView.trailingAnchor.constraint(equalTo: trailingAnchor),
collectionView.bottomAnchor.constraint(equalTo: collectionViewSeparator.topAnchor, constant: -8),
])
CodePudding user response:
I would suggest embedding the collection view and the separator view in a "container" UIView
.
Give the collection view Leading/Trailing (to the container view) and Height constraints, but no Bottom constraint (and no Top constraint yet).
Give the separator view Leading/Trailing (to the container view), Top to the collection view Bottom, and Height constraints, but no Bottom constraint.
Give the container view a height constraint so collection view and separator view (and maybe a little spacing) will fit.
Add "visible" and "hidden" constraints as var properties:
var cvVisibleConstraint: NSLayoutConstraint!
var cvHiddenConstraint: NSLayoutConstraint!
then, when we're setting all the other constraints, create those two like this:
// collectionView TOP constrained to TOP of container when visible
cvVisibleConstraint = collectionView.topAnchor.constraint(equalTo: containerView.topAnchor, constant: 8.0)
// collectionView TOP constrained to BOTTOM of container when hidden
cvHiddenConstraint = collectionView.topAnchor.constraint(equalTo: containerView.bottomAnchor, constant: 8.0)
To show/hide the collection view (and the separator, because it's constrained to the collection view):
containerView.clipsToBounds = true
and:
cvVisibleConstraint.isActive.toggle()
cvHiddenConstraint.isActive = !cvVisibleConstraint.isActive
UIView.animate(withDuration: 0.5, animations: {
self.view.layoutIfNeeded()
})
Here's an example... I'm adding a view to the main view to simulate the input accessory view, but the approach is the same:
class ShowHideVC: UIViewController {
var collectionView: UICollectionView!
let collectionViewSeparator = UIView()
let containerView = UIView()
let myInputAccessoryView = UIView()
var cvVisibleConstraint: NSLayoutConstraint!
var cvHiddenConstraint: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
let fl = UICollectionViewFlowLayout()
fl.scrollDirection = .horizontal
fl.itemSize = CGSize(width: 72.0, height: 72.0)
collectionView = UICollectionView(frame: .zero, collectionViewLayout: fl)
[myInputAccessoryView, containerView, collectionViewSeparator, collectionView].forEach { v in
v?.translatesAutoresizingMaskIntoConstraints = false
}
containerView.addSubview(collectionView)
containerView.addSubview(collectionViewSeparator)
myInputAccessoryView.addSubview(containerView)
view.addSubview(myInputAccessoryView)
let g = view.safeAreaLayoutGuide
// collectionView TOP constrained to TOP of container when visible
cvVisibleConstraint = collectionView.topAnchor.constraint(equalTo: containerView.topAnchor, constant: 8.0)
// collectionView TOP constrained to BOTTOM of container when hidden
cvHiddenConstraint = collectionView.topAnchor.constraint(equalTo: containerView.bottomAnchor, constant: 8.0)
// setting priorities to (999) avoids auto-layout complaints when toggling active
cvVisibleConstraint.priority = .required - 1
cvHiddenConstraint.priority = .required - 1
NSLayoutConstraint.activate([
myInputAccessoryView.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
myInputAccessoryView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 8.0),
myInputAccessoryView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -8.0),
myInputAccessoryView.heightAnchor.constraint(equalToConstant: 200.0),
containerView.topAnchor.constraint(equalTo: myInputAccessoryView.topAnchor, constant: 8.0),
containerView.leadingAnchor.constraint(equalTo: myInputAccessoryView.leadingAnchor, constant: 8.0),
containerView.trailingAnchor.constraint(equalTo: myInputAccessoryView.trailingAnchor, constant: -8.0),
containerView.heightAnchor.constraint(equalToConstant: 100.0),
// start with collection view showing
cvVisibleConstraint,
collectionView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: 8.0),
collectionView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: -8.0),
collectionView.heightAnchor.constraint(equalToConstant: 78.0),
collectionViewSeparator.topAnchor.constraint(equalTo: collectionView.bottomAnchor, constant: 8.0),
collectionViewSeparator.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: 8.0),
collectionViewSeparator.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: -8.0),
collectionViewSeparator.heightAnchor.constraint(equalToConstant: 1.0),
// no bottom constraints for collectionView or collectionViewSeparator
])
collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "c")
collectionView.dataSource = self
collectionView.delegate = self
// colors so we can see framing
collectionView.backgroundColor = .systemGreen
collectionViewSeparator.backgroundColor = .red
containerView.backgroundColor = .yellow
myInputAccessoryView.backgroundColor = .systemYellow
// comment / un-comment the next line to see what's really going on
containerView.clipsToBounds = true
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
cvVisibleConstraint.isActive.toggle()
cvHiddenConstraint.isActive = !cvVisibleConstraint.isActive
UIView.animate(withDuration: 0.5, animations: {
self.view.layoutIfNeeded()
})
}
}
extension ShowHideVC: UICollectionViewDataSource, UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 10
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let c = collectionView.dequeueReusableCell(withReuseIdentifier: "c", for: indexPath)
c.contentView.backgroundColor = .green
c.contentView.layer.cornerRadius = 8
return c
}
}
It will toggle between showing and hidden on any tap on the screen (animated) and look like this:
Note the last line in viewDidLoad()
:
// comment / un-comment the next line to see what's really going on
containerView.clipsToBounds = true