Swift - Hero animation to dismiss View - ios

I am having trouble using the Hero Library to dismiss my ViewController with custom animation.
In the end I would like to have pretty much the exact same animation as in this video:
preferred dismiss animation
So far my dismiss animation looks like this:
my dismiss animation so far
I am having 3 major problems which I can not figure out:
1.
When presenting/dismissing my ViewController there seems to be this white background behind my 2nd ViewController but I would like to just cover my first ViewController with the 2nd without any white views.
2.
My image disappears after the user starts swiping down behind my views when instead it should not change its position (preferred dismiss animation) until the view actually dismisses. Same things goes for the add-Button in the bottom right corner.
3. Like in the preferred dismiss animation my the backgroundView(lightGray View in the 2nd video) should dismiss a bit fast then the other subviews. I tried using the cascade-modifier but couldn't get that effect.
This is my 2nd ViewController:
override func viewDidLoad() {
super.viewDidLoad()
self.wishlistBackgroundView.hero.isEnabled = true
self.wishlistBackgroundView.heroID = "wishlistView"
self.wishlistBackgroundView.hero.modifiers = [.fade, .translate(CGPoint(x: 0, y: 800), z: 20)]
// adding panGestureRecognizer
panGR = UIPanGestureRecognizer(target: self,
action: #selector(handlePan(gestureRecognizer:)))
view.addGestureRecognizer(panGR)
self.wishlistLabel.text = wishList.name
self.wishlistImage.image = wishList.image
self.theTableView.wishList = wishList.wishData
self.theTableView.tableView.reloadData()
view.addSubview(wishlistBackgroundView)
view.addSubview(dismissWishlistViewButton)
view.addSubview(menueButton)
wishlistBackgroundView.addSubview(wishlistView)
wishlistBackgroundView.addSubview(wishlistLabel)
wishlistBackgroundView.addSubview(wishlistImage)
wishlistView.addSubview(theTableView.tableView)
wishlistView.addSubview(addWishButton)
NSLayoutConstraint.activate([
// constrain wishlistView
wishlistBackgroundView.topAnchor.constraint(equalTo: view.topAnchor),
wishlistBackgroundView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
wishlistBackgroundView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
wishlistBackgroundView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
wishlistView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 160.0),
wishlistView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0),
wishlistView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 0),
wishlistView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: 0),
// constrain wishTableView
theTableView.view.topAnchor.constraint(equalTo: wishlistView.topAnchor, constant: 60.0),
theTableView.view.bottomAnchor.constraint(equalTo: wishlistView.bottomAnchor, constant: 0),
theTableView.view.leadingAnchor.constraint(equalTo: wishlistView.safeAreaLayoutGuide.leadingAnchor, constant: 30.0),
theTableView.view.trailingAnchor.constraint(equalTo: wishlistView.safeAreaLayoutGuide.trailingAnchor, constant: -30.0),
// constrain dismissButton
dismissWishlistViewButton.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20),
dismissWishlistViewButton.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 23.0),
// constrain menueButton
menueButton.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20),
menueButton.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -25.0),
// constrain wishlistImage
wishlistImage.topAnchor.constraint(equalTo: wishlistView.topAnchor, constant: -70),
wishlistImage.leadingAnchor.constraint(equalTo: wishlistView.leadingAnchor, constant: 30),
wishlistImage.widthAnchor.constraint(equalToConstant: 90),
wishlistImage.heightAnchor.constraint(equalToConstant: 90),
//constrain wishlistlabel
wishlistLabel.topAnchor.constraint(equalTo: wishlistView.topAnchor, constant: -47),
wishlistLabel.leadingAnchor.constraint(equalTo: wishlistImage.leadingAnchor, constant: 100),
addWishButton.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -20),
addWishButton.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -40),
])
// set DeleteWishDelegate protocol for the table
theTableView.deleteWishDelegate = self
}
// define a small helper function to add two CGPoints
func addCGPoints (left: CGPoint, right: CGPoint) -> CGPoint {
return CGPoint(x: left.x + right.x, y: left.y + right.y)
}
// handle swqipe down gesture
#objc private func handlePan(gestureRecognizer:UIPanGestureRecognizer) {
// calculate the progress based on how far the user moved
let translation = panGR.translation(in: nil)
let progress = translation.y / 2 / view.bounds.height
switch panGR.state {
case .began:
// begin the transition as normal
dismiss(animated: true, completion: nil)
case .changed:
Hero.shared.update(progress)
// update views' position based on the translation
let viewPosition = CGPoint(x: wishlistBackgroundView.center.x, y: translation.y + wishlistBackgroundView.center.y)
Hero.shared.apply(modifiers: [.position(viewPosition)], to: self.wishlistBackgroundView)
default:
// finish or cancel the transition based on the progress and user's touch velocity
if progress + panGR.velocity(in: nil).y / view.bounds.height > 0.3 {
Hero.shared.finish()
} else {
Hero.shared.cancel()
}
}
}
I couldn't find any good tutorials on this nor anything on these topics on git. If anyone knows even one answer to any of the problems I am more than happy.
Technically these are 3 questions but they are quite related. If this is against SO rules, I am happy to ask them separately.

Related

Animating UICollectionView With Auto Constraints Swift

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),
])
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

iOS: How to scroll navigation bar as view controller scrolls

I want to scroll the navigation bar as the user scrolls on the view controller. This should be similar to how the YouTube app's home page is working. When the user scrolls down, the navigation bar should be made visible. The navigation bar should move as much as the scroll amount.
I'm aware of hidesBarOnSwipe and setNavigationBarHidden, but these do not give precise control of the y-axis. I'm also reading that Apple does not support directly modifying the navigation bar frame.
So, how does YouTube do this? I'm looking for an MVP demonstrating navigation bar position change along with a UIScrollView offset change.
Without additional detail about what you want to do, I'll make some guesses.
First, the top of the YouTube app's home page is almost certainly not a UINavigationBar -- it doesn't behave like one, there is no pushing/popping of controllers going on, it's in a tab bar controller setup, etc.
So, let's assume it's a view with subviews - we'll call it a "sliding header view" - and your goal is:
don't let the header view's top scroll down
"push it up" when scrolling up
"pull it down" when scrolling down
We can accomplish this by constraining the Top of that header view to the Top of the scroll view's Frame Layout Guide.
when we start to scroll, we'll save the current .contentOffset.y
when we scroll, we'll get the relative scroll y distance
if we're scrolling Up, we'll change the Top Constraint .constant value to move the header view up
if we're scrolling Down, we'll change the Top Constraint .constant value to move the header view down
Here's how it will look at the start:
as we scroll up just a little:
after we've scrolled up farther:
as we scroll down just a little:
after we've scrolled down farther:
Here's the example code for that:
Simple two-label "header" view
class SlidingHeaderView: UIView {
// simple view with two labels
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() {
backgroundColor = .systemBlue
let v1 = UILabel()
v1.translatesAutoresizingMaskIntoConstraints = false
v1.text = "Label 1"
v1.backgroundColor = .yellow
addSubview(v1)
let v2 = UILabel()
v2.translatesAutoresizingMaskIntoConstraints = false
v2.text = "Label 2"
v2.backgroundColor = .yellow
addSubview(v2)
NSLayoutConstraint.activate([
v1.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 12.0),
v1.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -12.0),
v2.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 12.0),
v2.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -12.0),
v1.topAnchor.constraint(equalTo: topAnchor, constant: 8.0),
v2.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -8.0),
v2.topAnchor.constraint(equalTo: v1.bottomAnchor, constant: 4.0),
v2.heightAnchor.constraint(equalTo: v1.heightAnchor),
])
}
}
example view controller
class SlidingHeaderViewController: UIViewController {
let scrollView: UIScrollView = {
let v = UIScrollView()
v.contentInsetAdjustmentBehavior = .never
return v
}()
let slidingHeaderView: SlidingHeaderView = {
let v = SlidingHeaderView()
return v
}()
let contentView: UIView = {
let v = UIView()
v.backgroundColor = .systemYellow
return v
}()
// Top constraint for the slidingHeaderView
var slidingViewTopC: NSLayoutConstraint!
// to track the scroll activity
var curScrollY: CGFloat = 0
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBackground
[scrollView, slidingHeaderView, contentView].forEach { v in
v.translatesAutoresizingMaskIntoConstraints = false
}
// add contentView and slidingHeaderView to the scroll view
[contentView, slidingHeaderView].forEach { v in
scrollView.addSubview(v)
}
// add scroll view to self.view
view.addSubview(scrollView)
let safeG = view.safeAreaLayoutGuide
let contentG = scrollView.contentLayoutGuide
let frameG = scrollView.frameLayoutGuide
// we're going to change slidingHeaderView's Top constraint relative to the Top of the scroll view FRAME
slidingViewTopC = slidingHeaderView.topAnchor.constraint(equalTo: frameG.topAnchor, constant: 0.0)
NSLayoutConstraint.activate([
// scroll view Top to view Top
scrollView.topAnchor.constraint(equalTo: safeG.topAnchor, constant: 0.0),
// scroll view Leading/Trailing/Bottom to safe area
scrollView.leadingAnchor.constraint(equalTo: safeG.leadingAnchor, constant: 0.0),
scrollView.trailingAnchor.constraint(equalTo: safeG.trailingAnchor, constant: 0.0),
scrollView.bottomAnchor.constraint(equalTo: safeG.bottomAnchor, constant: 0.0),
// constrain slidingHeaderView Top to scroll view's FRAME
slidingViewTopC,
// slidingHeaderView to Leading/Trailing of scroll view FRAME
slidingHeaderView.leadingAnchor.constraint(equalTo: frameG.leadingAnchor, constant: 0.0),
slidingHeaderView.trailingAnchor.constraint(equalTo: frameG.trailingAnchor, constant: 0.0),
// no Height or Bottom constraint for slidingHeaderView
// content view Top/Leading/Trailing/Bottom to scroll view's CONTENT GUIDE
contentView.topAnchor.constraint(equalTo: contentG.topAnchor, constant: 0.0),
contentView.leadingAnchor.constraint(equalTo: contentG.leadingAnchor, constant: 0.0),
contentView.trailingAnchor.constraint(equalTo: contentG.trailingAnchor, constant: 0.0),
contentView.bottomAnchor.constraint(equalTo: contentG.bottomAnchor, constant: 0.0),
// content view Width to scroll view's FRAME
contentView.widthAnchor.constraint(equalTo: frameG.widthAnchor, constant: 0.0),
])
// add some content to the content view so we have something to scroll
addSomeContent()
// because we're going to track the scroll offset
scrollView.delegate = self
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if slidingHeaderView.frame.height == 0 {
// get the size of the slidingHeaderView
let sz = slidingHeaderView.systemLayoutSizeFitting(CGSize(width: scrollView.frame.width, height: .greatestFiniteMagnitude), withHorizontalFittingPriority: .required, verticalFittingPriority: .defaultLow)
// use its Height for the scroll view's Top contentInset
scrollView.contentInset = UIEdgeInsets(top: sz.height, left: 0, bottom: 0, right: 0)
}
}
func addSomeContent() {
// create a vertical stack view with a bunch of labels
// and add it to our content view so we have something to scroll
let stack = UIStackView()
stack.axis = .vertical
stack.spacing = 32
stack.backgroundColor = .gray
stack.translatesAutoresizingMaskIntoConstraints = false
for i in 1...20 {
let v = UILabel()
v.text = "Label \(i)"
v.backgroundColor = UIColor(white: 0.9, alpha: 1.0)
v.heightAnchor.constraint(equalToConstant: 48.0).isActive = true
stack.addArrangedSubview(v)
}
contentView.addSubview(stack)
NSLayoutConstraint.activate([
stack.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 16.0),
stack.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16.0),
stack.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -16.0),
stack.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -16.0),
])
}
}
extension SlidingHeaderViewController: UIScrollViewDelegate {
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
curScrollY = scrollView.contentOffset.y
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let diffY = scrollView.contentOffset.y - curScrollY
var newY: CGFloat = slidingViewTopC.constant - diffY
if diffY < 0 {
// we're scrolling DOWN
newY = min(newY, 0.0)
} else {
// we're scrolling UP
if scrollView.contentOffset.y <= -slidingHeaderView.frame.height {
newY = 0.0
} else {
newY = max(-slidingHeaderView.frame.height, newY)
}
}
// update slidingHeaderView Top constraint constant
slidingViewTopC.constant = newY
curScrollY = scrollView.contentOffset.y
}
}
Everything is done via code - no #IBOutlet or #IBAction connections needed.

AutoLayout not updating views programmatically - Swift

I have the following code where I'm creating and laying out everything in code. What I'm trying to do is modify the myTopContainer when a button is tapped but I can not make the width changed, only the height gets modified.
Any idea why?
func setupAutoLayout(){
NSLayoutConstraint.activate([
// My Container
myTopContainer.bottomAnchor.constraint(equalTo: myButton.topAnchor, constant: -5),
myTopContainer.heightAnchor.constraint(equalToConstant: 50),
myTopContainer.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 15),
myTopContainer.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -15),
// My Button
myButton.widthAnchor.constraint(equalToConstant: 80),
myButton.heightAnchor.constraint(equalToConstant: 40),
myButton.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -15),
myButton.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: -400)
])
}
#IBAction func relayoutViews(_ sender: Any) {
UIView.animate(withDuration: 0.5, delay: 0, options: .curveEaseIn, animations: {
self.myTopContainer.heightAnchor.constraint(equalToConstant: 50).isActive = false
self.myTopContainer.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 15).isActive = false
// this is working, it changes the height
self.myTopContainer.heightAnchor.constraint(equalToConstant: 10).isActive = true
// this is not doing anything
self.myTopContainer.leadingAnchor.constraint(equalTo: self.myButton.trailingAnchor).isActive = true
self.view.layoutIfNeeded()
})
}
A line like this
self.myTopContainer.heightAnchor.constraint(equalToConstant: 50).isActive = false
Creates a new constraint every time. It doesn't modify the previously created constraint.
When you setupAutolayout you should save a reference to the constraints you want to later modify before actually calling NSLayoutConstraint.activate() on them.
Later you can set isActive to false on them.
For example:
var someConstraint: NSLayoutConstraint?
func setupAutoLayout(){
let aConstraint = myTopContainer.bottomAnchor.constraint(equalTo: myButton.topAnchor, constant: -5)
someConstraint = aConstraint
NSLayoutConstraint.activate([
// My Container
aConstraint
])
}
func editConstraint() {
self.someConstraint?.isActive = false
}
Anyway be careful on logs when you edit constraints, it's probable that you could break something while editing. If you see those logs of broken constraint than it means that something is still wrong (even if it visually looks good, it could crash or not work properly in some other situation)

Make a View stick to bottom with UITableViewController

I want to make a UIView stick on the bottom while I am scrolling in my UITableView.
My idea was to set a UIView with the position of right above the navigation item. Set it's zPosition to 1.
The problem is, that the yPosition of my UITableView varies.
Any idea how to solve this?
Edit:
Providing Screenshots for visible vs. expected behaviour:
Visible:
This is when I scroll:
Expected:
As seen on Tinder Camera Symbol above Table:
Edit2:
This code is what I use to put the rectangle to the bottom.
It works until I swipe the UITableView - The rectangle also scrolls up.
let bounds = self.view.bounds
let yPosition = self.navigationController?.toolbar.frame.minY
print(yPosition)
let myView = UIView(frame: CGRect(x: 0, y: yPosition! - bounds.height/6, width: bounds.width, height: bounds.height/6))
myView.backgroundColor = myColor.rookie
myView.alpha = 0.8
myView.layer.zPosition = 1
myView.isUserInteractionEnabled = true
self.view.addSubview(myView)
there is a solution for this. you can do this by disabling the Auto Layout(button.translatesAutoresizingMaskIntoConstraints = false) property of the corresponding Button or any UIView for floating button:
Swift 4
button.translatesAutoresizingMaskIntoConstraints = false
if #available(iOS 11.0, *) {
button.rightAnchor.constraint(equalTo: tableView.safeAreaLayoutGuide.rightAnchor, constant: -10).isActive = true
button.bottomAnchor.constraint(equalTo: tableView.safeAreaLayoutGuide.bottomAnchor, constant: -10).isActive = true
} else {
button.rightAnchor.constraint(equalTo: tableView.layoutMarginsGuide.rightAnchor, constant: 0).isActive = true
button.bottomAnchor.constraint(equalTo: tableView.layoutMarginsGuide.bottomAnchor, constant: -10).isActive = true
}

Rotating view using constraints - Swift

I'm trying to rotate a uiview on top of another view using constraints. Here is what I implemented
mediaContainer.addSubview(overlayView)
keepBottomLeft()
overlayView.layoutIfNeeded()
func keepBottomLeft(overlayView: UIView) {
NSLayoutConstraint(item: overlayView, attribute:.width, relatedBy:.equal,toItem: mediaContainer, attribute:.width, multiplier: 0.8, constant: 0).isActive = true
NSLayoutConstraint(item: overlayView, attribute:.leading, relatedBy:.equal, toItem: mediaContainer, attribute:.leading, multiplier: 1, constant: 0).isActive = true
NSLayoutConstraint(item: overlayView, attribute:.height, relatedBy:.equal,toItem: nil, attribute:.notAnAttribute, multiplier: 1.0, constant: 26).isActive = true
NSLayoutConstraint(item: overlayView, attribute:.bottom, relatedBy:.equal, toItem: mediaContainer, attribute:.bottom, multiplier: 1, constant: 0).isActive = true
overlayView.transform = CGAffineTransform(rotationAngle: CGFloat(-M_PI_2))
}
Will it be necessary to use an anchor point? This is what I'm trying to achieve?
What I get is
Can someone point me in the right direction?
Your anchor point is at the yellow view's center; the default. Either you want to move it to the bottom left with layer.anchorPoint = CGPoint(x: 0, y:1) [Note the anchor is in unit coordinates form 0 to 1, not in points]. You will still need an extra translate of x = height.
Alternatively you can concatenate your transforms and move your center to the point you want to rotate about, perform the rotation, and then reverse your translation (this is actually the typical way to do it with 3d transforms).
UPDATE:
Here is a playground. Note that you concatenate the matrices in the reverse order that you want the transforms to be applied. I have an animation in here so you can decompose the transforms and see what each one does:
Move the view to the origin (0,0) (this is the last transform)
Rotate the view 90 degrees clockwise
Move the view over by half of its current width and down so its at the bottom of the superview.
import PlaygroundSupport
import UIKit
class V: UIViewController {
var overlayView = UIView()
override func viewDidLoad() {
super.viewDidLoad()
overlayView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(overlayView)
overlayView.backgroundColor = .yellow
overlayView.heightAnchor.constraint(equalToConstant: 26).isActive = true
overlayView.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.8).isActive = true
overlayView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
overlayView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
}
override func viewDidAppear(_ animated: Bool) {
UIView.animate(withDuration: 1) {
let center = self.overlayView.center
let size = self.overlayView.bounds.size
self.overlayView.transform =
CGAffineTransform(translationX: self.view.bounds.size.height - size.width / 2, y: -size.height/2)
.concatenating(CGAffineTransform(rotationAngle: CGFloat.pi/2)
.concatenating(CGAffineTransform(translationX: -center.x, y: -center.y)))
}
}
}
let v = V()
v.view.frame = CGRect(x: 0, y: 0, width: 200, height: 200)
PlaygroundPage.current.liveView = v

Resources