I have a UITextView and I want to scale it up and down with animation, I tried to scale it like other UIViews, like this:
self.textView.widthConstraint.constant = newWidth
UIView.animate(withDuration: 0.4, animations: {
self.view.layoutIfNeeded()
}, completion: nil)
The animation is a bit weird: the text in the text view is always the same size from the beginning of the animation till the end, with the container scaling up the text simply goes from right to left.
What's the proper way to scale a UITextView along with the text it contains?
If you just want to animate the UITextView scaling, I think it would be better to use CGAffineTransform. You can do it something like this...
UIView.animate(withDuration: 0.4, animations: {
self.textView.transform = self.textView.transform.scaledBy(x: 0.2, y: 0.2)
})
Try this:
self.textView.widthConstraint.constant = newWidth
UIView.animate(withDuration: 0.4, animations: {
self.textView.layoutIfNeeded()
})
Update
According to this SO post answer, you have to use Affine Transform:
The Affine Transform scaling factor is applied to text like anything else. So the onscreen text size should be the nominal textsize times the scaling factor.
Related
I have a custom view that acts as a progress view, which is basically a simple set of views where I animate the leading constraints of the container that holds the value and of the view that acts as a 'filled' view, so it looks like this:
This is a reusable view and is used in several places in the app. Some of the users suffer a crash that is related to the animation in the update method of this custom view, and I can't reproduce it nor I can find the issue. The method looks like this:
extension GradientProgressView {
/// Animates the progress view to the desired % value and changes the value label
/// - Parameter percentage: the desired % value in range [0.0 - 1.0]
func updateProgress(percentage: Double) {
UIView.animate(withDuration: 0.4, delay: 0, options: .transitionCrossDissolve) {
self.labelProgress.text = "\(Int(percentage * 100))%"
}
// limit to 100% for correct constraint calculation
let percentageAdapted = CGFloat(min(percentage, 1.0))
// the available width for the value container to move left/right
let availableValueContainerWidth = backgroundView.frame.width - labelGradientView.frame.width
labelContainerLeadingConstraint.constant = min(availableValueContainerWidth * percentageAdapted, backgroundView.frame.width - 50)
foregroundLeadingConstraint.constant = labelContainerLeadingConstraint.constant
UIView.animate(withDuration: 0.6, delay: 0, options: .curveEaseOut) {
self.layoutIfNeeded()
}
}
}
More precisely, the crash happens in the animation block of the first UIView.animate call, which corresponds to the line 93 in the stacktrace (see below):
UIView.animate(withDuration: 0.4, delay: 0, options: .transitionCrossDissolve) {
self.labelProgress.text = "\(Int(percentage * 100))%"
}
Here's the stacktrace of the crash:
I've tried using the self as a weak reference in both animation blocks, but the crash reappeared. Could someone point out to me what am I doing wrong here?
It's very tough to debug a crash that can't be reproduced, but a couple comments (that may or may not have anything to do with the crash)...
First, this block:
UIView.animate(withDuration: 0.4, delay: 0, options: .transitionCrossDissolve) {
self.labelProgress.text = "\(Int(percentage * 100))%"
}
doesn't really do anything, other than change the text.
If we want to cross-dissolve the text change in a label, we need to use UIView.transition(...):
UIView.transition(with: self.pctLabel, duration: 0.4, options: .transitionCrossDissolve, animations: {
self.pctLabel.text = "\(Int(percentage * 100))%"
})
Second, you might be running into a problem with your availableValueContainerWidth calculation, as you are allowing the label width to change based on its text. If the current value is only 1-digit, and we change it to 100%, we might get something like this:
Giving the label a fixed width (calculate the width needed for the max value text of "100%"), it can look like this:
Of course, we could use a variable-width label and "get around" the issue by using the center instead of leading ... but the cross-dissolve and width-change animation may also look a little quirky.
Again, really tough to debug a non-reproducible crash, but those two things may be something to look at.
I'm trying to achieve some kind of "parallax" effect.
Let's say i have an image at the top of the screen, with height: 180 and a scrollview under it.
The initial state is:
state initial
When i scroll up more than 32 px, the image should "dock" up, like making his height 45px, and doing this animated.
The final state should be:
state final
In scrollViewDidEndDragging, i want to dock the image up, animated. I used the following code:
self.imageConstraint?.constant = 45.0
UIView.animate(withDuration: 1.0,
delay: 0,
options: [.beginFromCurrentState,
.allowAnimatedContent,
.allowUserInteraction],
animations: {
self.superview?.layoutIfNeeded()
}, completion: { _ in
completion()
})
The problem is that the image is set to 45 height, but only the image, and a blank space remains ( the initial height-final height ) and that space is animated.
Basically, with no deceleration, when scrollDidEndDragging i want to animate the height of the image and this does not work as expected.
Looks like this:
during animation
What i am doing wrong ?
Write your code in this way it will work hopefully,
UIView.animate(withDuration: 0.5)
{
self. self.imageConstraint.constant = 45.0
self.view.layoutIfNeeded();
}
Do let me if it doesn't. Thanks
In the AppStore (iOS 11) on the left "today"-tab, there are several card views. If you highlight one, it shrinks a little bit. How can I rebuild this animation?
I guess changing the constraints of the card view during an animation will not be what we need, since you would also have to adapt all the other constraints (e.g. of the labels) to match the new size.
Is there an easier way to shrink a view with all its subviews?
Also, when you click the card, it increases to fullscreen with an animation. Do you have any ideas how to achieve this effect?
For tapping and shrinking card, I also wrote about this in detail. Here's the idea:
Use a scaling transform to animate shrinking (like in accepted answer)
Disable delaysContentTouch to make it shrink faster upon touch
(scrollView.delaysContentTouch = false)
Always allow users to scroll using .allowUserInteraction animation option:
UIView.animate(withDuration: 1.0,
delay: 0.0,
options: [.allowUserInteraction],
animations: ...,
completion: ...)
(By default when you use transform, it disables the interaction a bit. User can't scroll successively without doing that)
About the expanding to full screen with animation, I have tried to replicate it with the native's transition APIs which you can check out here: https://github.com/aunnnn/AppStoreiOS11InteractiveTransition
In short, I use UIViewControllerAnimatedTransitioning to do custom animation. Hide the original card and create a new dummy card view just for animation. Then setup AutoLayout constraints of that card, including 4 to each of the screen edges. Then animate those constraints to make it fill the screen.
After everything is done, hide that dummy view and show the destination detail page.
Note: The exact implementation detail is a bit different and involved.
You can get an easy scale animation using transform:
UIView.animate(withDuration: 0.2) {
view.transform = CGAffineTransform.identity.scaledBy(x: 0.9, y: 0.9)
}
As to the fullscreen animation, you want to check out some tutorials on how to create custom transition animations.
If you are interested in a more complete functionality you can use this library:
https://github.com/PaoloCuscela/Cards
this is also a good rebuild of that animation when you press a card:
UIView.animate(withDuration: 0.5, delay: 0.0, usingSpringWithDamping: 0.8, initialSpringVelocity: 0.8, options: .beginFromCurrentState, animations: {
self.transform = .init(scaleX: 0.95, y: 0.95)
}, completion: nil)
Try to animate the width of a UIView like this. But it does not do anything. Why? The commented part changes alpha of the view, and that works.
UIView.animate(withDuration: 1, delay: 0, options: [.curveEaseInOut], animations: {
self.constraintLookupWidth.constant = 300
self.viewLookup.setNeedsLayout()
//self.viewLookup.alpha = 0.2
}, completion: {complete in
self.constraintLookupWidth.constant = 200
self.viewLookup.setNeedsLayout()
//self.viewLookup.alpha = 1.0
})
I guess that you need to integrate the call with a layoutIfNeeded(), this ask auto layout to force the layout right now.
I have a piece of core animation by use of 2 methods in order to give appearance that the object is growing in size from the centre-outwards from the existing location...
(1) viewDidLayoutSubviews()
self.imageView.frame = CGRectMake(187, 249, 0, 0)
which is in the centre of the screen for this imageView object
(2) viewDidAppear()
- with animation block of duration 1 second. e.g.
UIView.animateWithDuration(1.0, delay: 1.5, options: [], animations: {
self.imageView.frame = CGRectMake(16, 70, 343, 358)
}, completion: nil)
which puts object where I want it to show in full size, for the specific screen size.
When I use the x and y coordinates for CGRectMake(...) on the iPhone size I'm looking for they work fine, But when I try on simulator with different screen sizes, it doesn't work in conjunction with the Autolayout constraints, but overrides the existing Constraints.
Question(s)...
Is there a way of making this animation work by centering it proportional to the screen size somehow, rather than explicit coordinates and sizes? via CGRectMake...? (...which seems to show a clash between use of Autolayout and Animation)
Ideally I'm looking for an elegant simple solution? or am I at the limits of the tech?
Many thanks in advance!
You can set imageView location using autolayout constraints & give it a size constraint and by taking an outlet from it you can animate height to increase leaving all other constraints working
#IBOutlet var myViewHeight: NSLayoutConstraint!
UIView.animateWithDuration(0.3, delay: 0.0, options: [], animations:
{
() -> Void in
self.myViewHeight.constant = 300
self.view.layoutIfNeeded()
}, completion:nil)
You should take the outlet from the constraint itself like control+drag on Label.top = topMargin