UIStackView subviews animating only when isHidden is set to false - swift

I am trying to hide/show my subview of a UIStackView within an animation block like so:
UIView.animate(withDuration: 0.3) {
self.unpairSensorButton.isHidden = isHidden
}
Showing animation works perfectly, but when I try to hide it, it just waits for the animation duration and then just immediately disappears. Any idea why?
I have tried to use the layoutIfNeeded() and putting it into DispatchQueue.main.async block bot neither helped.

isHidden property is not animatable by itself.
UIStackView basically changes its internal constraints to adapt its size when you hide some subview. Since constraints are animatable, you would do this:
unpairSensorButton.isHidden = isHidden
UIView.animate(withDuration: 0.3) {
self.view.layoutIfNeeded()
}
Calling layoutIfNeeded() in an animation block will animate the layout process.

Related

UITextField shrinks text when animation starts

When I try to animate UiTextField width constraints, it makes content looking like on the final animation frame (just shrinks it). How to avoid that and make animation forwards same as you see it works backwards? (animation slowed down in emulator here)
textFieldWidthConstraint.constant = 0.0
UIView.animate(withDuration: 0.3) {
self.layoutIfNeeded()
}

Strange animation when changing height constraint on a CollectionView (autolayout)

I have a collectionView which I need to animate height changes for, so it expands up from the foot of the screen, & back down again. It's hard to explain what's going wrong so I've attached a recording of the simulator. The animation is fine for expanding, but very odd for the collapse - the content partially disappears as it animates, then reappears when it's done. The animation code is triggered during the scrollView 'scrollViewDidScroll(_ scrollView: UIScrollView)' delegate method, with logic to determine whether to collapse or expand. The expand animation, whose code is pretty much identical, works fine.
Animation code -
private func collapseTable() {
guard self.isTableChangingSize == false else { return } // as the delegate method is triggered during the animation, we use a flag to prevent multiple collapse calls
guard self.tableHeightConstraint.constant != self.tableCollapsedHeight else { return } // don't collapse if we're already collapsed
self.isTableChangingSize = true
self.tableHeightConstraint.constant = self.tableCollapsedHeight
UIView.animate(withDuration: 0.5, animations: {
self.view.layoutIfNeeded()
}) { _ in
self.isTableChangingSize = false
}
}
Here are a couple of videos of what's happening -
Normal speed
Slow motion
After far too long messing about trying different approaches, & tweaking everything, it seems that the problem was allowing scrolling during the animation block. If you set -
self.collectionView.isScrollEnabled = false
before the animation block, and -
self.collectionView.isScrollEnabled = true
in the completion, all is good.

UIPopoverPresentationController without overlay

I am implementing a popover view using UIPopoverPresentationController.
The trouble with this, is that by default, I have a shadow with a large radius for the controller.
I want to disable this - the overlay.
I have tried:
to customise the layout shadow (using a UIPopoverBackgroundView):
layer.shadowColor = UIColor.white.withAlphaComponent(0.01).cgColor
layer.shadowOffset = .zero
layer.shadowRadius = 0
In view debugging - I can see behind the popup 4 image views with gray gradient background:
I am sure this is a default behaviour, of showing an overlay behind a popover.
How do disable this?
I found this and this. But those didn't helped.
If you take a closer look at the views hierarchy you will notice that the shadow layer _UIMirrorNinePatchView is a sublayer of UITransitionView same as UIPopoverView - both are on the same level.
views hierarchy picture
In this case you can try to hide this sublayer like so:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if let shadowLayer = UIApplication.shared.windows.first?.layer.sublayers?[1].sublayers?[1] {
shadowLayer.isHidden = true
}
}
Make sure to hide it in viewDidLayoutSubviews to avoid exceptions related to missing sublayers or sublayer flickering.

UIScrollView scrollable area not updating with scrollView frame constraint update

I'm updating a frame constraint
scrollView.top Equal container.top
by subtracting from the constant, then calling layoutIfNeeded()
The scrollview's frame updates fine, and the content comes with it, but in the new area that the scrollview has acquired (above the scrollview) it does not fire scroll events when dragging. The old area works.
I've tried updating the contentOffset, but that effectively scrolls the content up inside the scrollview instead of expanding the scrollable area
I'm updating the contentSize, but it seems like problem is where the contentSize starts from
I've tried updating the contentInset, that still just moves the content around without updating the scrollable area
Maybe there is a property that needs to be updated but I can't find it
The end goal here is to programmatically increase the size of a scrollView
here is scrollViewDidScroll, where I'm updating the constraint
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let newVal:CGFloat = -120.0
DispatchQueue.main.async { [unowned self] in
self.feedTableViewTopConstraint.constant = newVal
self.view.layoutIfNeeded()
}
}
You should call
setNeedsLayout()
instead and if you want the layoutIfNeeded() to happen in the same loop then you can call it immediately after you called setNeedsLayout()

Cross-Disolve transition just changes instantly - Swift 3.0

I'm trying to change the color of a button's background using the cross dissolve animation, however, whenever I run this it instantly change the color instead of having a smooth transition.
UIView.transition(with: self.SignIn,
duration:3,
options: UIViewAnimationOptions.transitionCrossDissolve,
animations: { self.SignIn.backgroundColor? = UIColor(hexaString: "#" + hex) },
completion: nil)
Instead of setting backgroundColor in animations block I have set it before the view transition and then start the transition of view and it will work perfectly.
self.btnAnimate.backgroundColor = UIColor.red
UIView.transition(with: self.btnAnimate,
duration: 3,
options: .transitionCrossDissolve,
animations: nil,
completion: nil)
Output
Note: I have set red color as backgroundColor you can set what ever color you want.
This is happening because your animations are conflicting with the default button animations.
To solve this, you can create a custom child class of UIButton:
import UIKit
class CustomButton: UIButton {
func animateButton(hex: String) {
// This block disables the default animations
UIButton.performWithoutAnimation {
// This is where you define your custom animation
UIView.transition(with: self, duration:3, options: UIViewAnimationOptions.transitionCrossDissolve, animations: {
self.backgroundColor? = UIColor(hexaString: "#" + hex) }, completion: nil)
// You must call layoutIfNeeded() at the end of the block to prevent layout problems
self.layoutIfNeeded()
}
}
}
Then to call this function:
signInButton.animateButton("ffffff") // Pass in your hex string
Of course, be sure to change the class of your loginButton on the story board from UIButton to CustomButton.
The downside to this approach is that the text is still darkened during the animation process. There may be a way to override this (I wasn't able to quickly determine how), so an alternative approach to this solution is to use a UILabel instead of a UIButton and set an on click listener.
Or another approach you might want to consider, is put a customLabel behind a UIButton and perform animations on the label when the button is clicked. Although this approach seems somewhat "hacky", the advantage is that the button is not disabled while the label is being animated, meaning you can register rapid sequential button presses (although it looks like the purpose of your button is to log the user in, in which case this probably wouldn't be necessary for you).