Swift 3 - Code Update Problems In a Pod - ios

I updated my project to Swift 3 and I experienced problems in the following pod: https://github.com/Ramotion/expanding-collection and it no longer works :(
The problems are the following, I will explain each problem:
1- "Operator should no longer be declared with body; use a precedence group instead" yellow warning on the following line in the class "Constraints Helper":
infix operator >>>- { associativity left precedence 150}
How to write this in Swift 3?
2-"'>>>-' produces 'NSLayoutConstraint', not the expected contextual result type 'Void' (aka '()')" red error on the following line in the class "Constraints Helper":
func addScaleToFillConstratinsOnView(_ view: UIView) {
[NSLayoutAttribute.left, .right, .top, .bottom].forEach { attribute in
(self, view) >>>- { $0.attribute = attribute }
}
}
How to fix this issue?
3-"Result of operator '>>>-' is unused" yellow warning on the following lines in the AnimatingBarButton class:
fileprivate func configureImageView(_ imageView: UIImageView, imageName: String) {
guard let customView = customView else { return }
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.image = UIImage(named: imageName)
imageView.contentMode = .scaleAspectFit
// imageView.backgroundColor = .greenColor()
// imageView.layer.anchorPoint = CGPoint(x: 0.5, y: 0.5)
customView.addSubview(imageView)
//Result of operator '>>>-' is unused - HERE 1
[(NSLayoutAttribute.centerX, 12), (.centerY, -1)].forEach { info in
(customView, imageView) >>>- {
$0.attribute = info.0
$0.constant = CGFloat(info.1)
}
}
//Result of operator '>>>-' is unused - HERE 2
[NSLayoutAttribute.height, .width].forEach { attribute in
imageView >>>- {
$0.attribute = attribute
$0.constant = 20
}
}
}
Also I am getting the same error in this piece of code from the BasePageCollectionCell:
fileprivate func createShadowViewOnView(_ view: UIView?) -> UIView? {
guard let view = view else {return nil}
let shadow = Init(UIView(frame: .zero)) {
$0.backgroundColor = .clear
$0.translatesAutoresizingMaskIntoConstraints = false
$0.layer.masksToBounds = false;
$0.layer.shadowColor = UIColor.black.cgColor
$0.layer.shadowRadius = 10
$0.layer.shadowOpacity = 0.3
$0.layer.shadowOffset = CGSize(width: 0, height:0)
}
contentView.insertSubview(shadow, belowSubview: view)
for info: (attribute: NSLayoutAttribute, scale: CGFloat) in [(NSLayoutAttribute.width, 0.8), (NSLayoutAttribute.height, 0.9)] {
if let frontViewConstraint = view.getConstraint(info.attribute) {
//Result of operator '>>>-' is unused
shadow >>>- {
$0.attribute = info.attribute
$0.constant = frontViewConstraint.constant * info.scale
}
}
}
for info: (attribute: NSLayoutAttribute, offset: CGFloat) in [(NSLayoutAttribute.centerX, 0), (NSLayoutAttribute.centerY, 30)] {
//Result of operator '>>>-' is unused
(contentView, shadow, view) >>>- {
$0.attribute = info.attribute
$0.constant = info.offset
}
}
// size shadow
let width = shadow.getConstraint(.width)?.constant
let height = shadow.getConstraint(.height)?.constant
shadow.layer.shadowPath = UIBezierPath(roundedRect: CGRect(x: 0, y: 0, width: width!, height: height!), cornerRadius: 0).cgPath
return shadow
}
MARK:- PageCollectionView
//Result of operator '>>>-' is unused
collectionView >>>- {
$0.attribute = .height
$0.constant = height
}
How to fix this issue?
I know that this is long, and that the author needs to fix these issues, but the author is not responding or letting us know about an ETA of an update. And I was just about to finish up my app and then updated to Swift 3, I would really appreciate it if you could guide me through this. PS: Many people are facing similar issues in this library, I will share your solutions in the support page of that library.
Best,
Aa

Related

Scaling current dot of UIPageControl and keeping it centered

I've subclassed UIPageControl in order to have its current dot bigger.
class CustomPageControl: UIPageControl {
override var currentPage: Int {
didSet {
updateDots()
}
}
func updateDots() {
let currentDot = subviews[currentPage]
let largeScaling = CGAffineTransform(scaleX: 3, y: 3)
subviews.forEach {
// apply the large scale of newly selected dot
// restore the normal scale of previously selected dot
$0.transform = $0 == currentDot ? largeScaling : .identity
}
}
}
But the result of the transform isn't centered (the red dot should be aligned with the others):
I've tried (on iOS 12):
changing the frame or center of currentDot has no effect.
changing the transform to include a translatedBy(x: CGFloat, y: CGFloat) has no effect.
changing the constraints like here is making the first dot jumping:
currentDot.translatesAutoresizingMaskIntoConstraints = false
currentDot.centerYAnchor.constraint(equalTo: self.centerYAnchor, constant: 0)
currentDot.centerXAnchor.constraint(equalTo: self.centerXAnchor, constant: 0)
I got it finally working by rewriting all the subviews constraints by myself.
// https://stackoverflow.com/a/55063316/1033581
class DefaultPageControl: UIPageControl {
override var currentPage: Int {
didSet {
updateDots()
}
}
override func sendAction(_ action: Selector, to target: Any?, for event: UIEvent?) {
super.sendAction(action, to: target, for: event)
updateDots()
}
private func updateDots() {
let currentDot = subviews[currentPage]
let largeScaling = CGAffineTransform(scaleX: 3.0, y: 3.0)
let smallScaling = CGAffineTransform(scaleX: 1.0, y: 1.0)
subviews.forEach {
// Apply the large scale of newly selected dot.
// Restore the small scale of previously selected dot.
$0.transform = $0 == currentDot ? largeScaling : smallScaling
}
}
override func updateConstraints() {
super.updateConstraints()
// We rewrite all the constraints
rewriteConstraints()
}
private func rewriteConstraints() {
let systemDotSize: CGFloat = 7.0
let systemDotDistance: CGFloat = 16.0
let halfCount = CGFloat(subviews.count) / 2
subviews.enumerated().forEach {
let dot = $0.element
dot.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.deactivate(dot.constraints)
NSLayoutConstraint.activate([
dot.widthAnchor.constraint(equalToConstant: systemDotSize),
dot.heightAnchor.constraint(equalToConstant: systemDotSize),
dot.centerYAnchor.constraint(equalTo: centerYAnchor, constant: 0),
dot.centerXAnchor.constraint(equalTo: centerXAnchor, constant: systemDotDistance * (CGFloat($0.offset) - halfCount))
])
}
}
}
System constants in the code (7.0 and 16.0) are respectively the size and the distance found for a default UIPageControl dot on iOS 12.
I tried the solution proposed by Cœur in Swift 5 and Xcode 11 and it works fine with a few notes:
The PageControl element in IB/Storyboard has to be positioned with constraints.
The dots are slightly off-center but it can be quickly fixed by changing the constant of the last constraint to systemDotDistance * ( CGFloat($0.offset) - (halfCount - 0.5)).
If the updateConstraints override is never called, you might need to call self.view.setNeedsUpdateConstraints() in the view controller.

iOS complex animation coordination like Android Animator(Set)

I've made a fairly complex animation in my Android app using Animator classes. I want to port this animation to iOS. Preferably it is somewhat like the Android Animator. I've looked around and nothing seems to be what I want. The closest I got was with CAAnimation. But unfortunately all child delegates get ignored if they're put in a group.
Let me start with the animation I made on Android. I'm animating three view groups (which contains an ImageView and a TextView). Per button I have an animation which translates the view to the left and simultaneously animate the alpha to 0. After that animation there is another animation which translates the same view in from the right to the original position and also animates the alpha back to 1. There is one view which also has a scale animation besides the translate and alpha animation. All the views are using different timing functions (easing). The animating in and animating out is different and one view has a different timing function for the scale while the alpha and translate animation uses the same. After the first animation ends I'm setting the values to prepare the second animation. The duration of the scale animation is also shorter than the translate and alpha animation. I'm putting the single animations (translate and alpha) inside an AnimatorSet (basically a group for animations). This AnimatorSet is put in another AnimatorSet to run the animations after eachother (first animate and than in). And this AnimatorSet is put in another AnimatorSet which runs the animation of all 3 buttons simultaneously.
Sorry for the long explanation. But this way you understand how I'm trying to port this to iOS. This one is too complex for the UIView.animate(). CAAnimation overrides delegates if put into a CAAnimationGroup. ViewPropertyAnimator doesn't allow custom timing functions to my knowledge and can't coordinate multiple animations.
Does anybody have an idea what I could use for this? I'm also fine with a custom implementation which gives me a callback each animation tick so I can update the view accordingly.
Edit
The Android animation code:
fun setState(newState: State) {
if(state == newState) {
return
}
processing = false
val prevState = state
state = newState
val reversed = newState.ordinal < prevState.ordinal
val animators = ArrayList<Animator>()
animators.add(getMiddleButtonAnimator(reversed, halfAnimationDone = {
displayMiddleButtonState()
}))
if(prevState == State.TAKE_PICTURE || newState == State.TAKE_PICTURE) {
animators.add(getButtonAnimator(leftButton, leftButton, leftButton.imageView.width.toFloat(), reversed, halfAnimationDone = {
displayLeftButtonState()
}))
}
if(prevState == State.TAKE_PICTURE || newState == State.TAKE_PICTURE) {
animators.add(getButtonAnimator(
if(newState == State.TAKE_PICTURE) rightButton else null,
if(newState == State.CROP_PICTURE) rightButton else null,
rightButton.imageView.width.toFloat(),
reversed,
halfAnimationDone = {
displayRightButtonState(inAnimation = true)
}))
}
val animatorSet = AnimatorSet()
animatorSet.playTogether(animators)
animatorSet.start()
}
fun getButtonAnimator(animateInView: View?, animateOutView: View?, maxTranslationXValue: Float, reversed: Boolean, halfAnimationDone: () -> Unit): Animator {
val animators = ArrayList<Animator>()
if(animateInView != null) {
val animateInAnimator = getSingleButtonAnimator(animateInView, maxTranslationXValue, true, reversed)
if(animateOutView == null) {
animateInAnimator.addListener(object : AnimatorListenerAdapter() {
override fun onAnimationStart(animation: Animator?) {
halfAnimationDone()
}
})
}
animators.add(animateInAnimator)
}
if(animateOutView != null) {
val animateOutAnimator = getSingleButtonAnimator(animateOutView, maxTranslationXValue, false, reversed)
animateOutAnimator.addListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator?) {
halfAnimationDone()
}
})
animators.add(animateOutAnimator)
}
val animatorSet = AnimatorSet()
animatorSet.playTogether(animators)
return animatorSet
}
private fun getSingleButtonAnimator(animateView: View, maxTranslationXValue: Float, animateIn: Boolean, reversed: Boolean): Animator {
val translateDuration = 140L
val fadeDuration = translateDuration
val translateValues =
if(animateIn) {
if(reversed) floatArrayOf(-maxTranslationXValue, 0f)
else floatArrayOf(maxTranslationXValue, 0f)
} else {
if(reversed) floatArrayOf(0f, maxTranslationXValue)
else floatArrayOf(0f, -maxTranslationXValue)
}
val alphaValues =
if(animateIn) {
floatArrayOf(0f, 1f)
} else {
floatArrayOf(1f, 0f)
}
val translateAnimator = ObjectAnimator.ofFloat(animateView, "translationX", *translateValues)
val fadeAnimator = ObjectAnimator.ofFloat(animateView, "alpha", *alphaValues)
translateAnimator.duration = translateDuration
fadeAnimator.duration = fadeDuration
if(animateIn) {
translateAnimator.interpolator = EasingInterpolator(Ease.CUBIC_OUT)
fadeAnimator.interpolator = EasingInterpolator(Ease.CUBIC_OUT)
} else {
translateAnimator.interpolator = EasingInterpolator(Ease.CUBIC_IN)
fadeAnimator.interpolator = EasingInterpolator(Ease.CUBIC_IN)
}
val animateSet = AnimatorSet()
if(animateIn) {
animateSet.startDelay = translateDuration
}
animateSet.playTogether(translateAnimator, fadeAnimator)
return animateSet
}
fun getMiddleButtonAnimator(reversed: Boolean, halfAnimationDone: () -> Unit): Animator {
val animateInAnimator = getMiddleButtonSingleAnimator(true, reversed)
val animateOutAnimator = getMiddleButtonSingleAnimator(false, reversed)
animateOutAnimator.addListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator?) {
halfAnimationDone()
}
})
val animatorSet = AnimatorSet()
animatorSet.playTogether(animateInAnimator, animateOutAnimator)
return animatorSet
}
private fun getMiddleButtonSingleAnimator(animateIn: Boolean, reversed: Boolean): Animator {
val translateDuration = 140L
val scaleDuration = 100L
val fadeDuration = translateDuration
val maxTranslationXValue = middleButtonImageView.width.toFloat()
val translateValues =
if(animateIn) {
if(reversed) floatArrayOf(-maxTranslationXValue, 0f)
else floatArrayOf(maxTranslationXValue, 0f)
} else {
if(reversed) floatArrayOf(0f, maxTranslationXValue)
else floatArrayOf(0f, -maxTranslationXValue)
}
val scaleValues =
if(animateIn) floatArrayOf(0.8f, 1f)
else floatArrayOf(1f, 0.8f)
val alphaValues =
if(animateIn) {
floatArrayOf(0f, 1f)
} else {
floatArrayOf(1f, 0f)
}
val translateAnimator = ObjectAnimator.ofFloat(middleButtonImageView, "translationX", *translateValues)
val scaleXAnimator = ObjectAnimator.ofFloat(middleButtonImageView, "scaleX", *scaleValues)
val scaleYAnimator = ObjectAnimator.ofFloat(middleButtonImageView, "scaleY", *scaleValues)
val fadeAnimator = ObjectAnimator.ofFloat(middleButtonImageView, "alpha", *alphaValues)
translateAnimator.duration = translateDuration
scaleXAnimator.duration = scaleDuration
scaleYAnimator.duration = scaleDuration
fadeAnimator.duration = fadeDuration
if(animateIn) {
translateAnimator.interpolator = EasingInterpolator(Ease.QUINT_OUT)
scaleXAnimator.interpolator = EasingInterpolator(Ease.CIRC_OUT)
scaleYAnimator.interpolator = EasingInterpolator(Ease.CIRC_OUT)
fadeAnimator.interpolator = EasingInterpolator(Ease.QUINT_OUT)
} else {
translateAnimator.interpolator = EasingInterpolator(Ease.QUINT_IN)
scaleXAnimator.interpolator = EasingInterpolator(Ease.CIRC_IN)
scaleYAnimator.interpolator = EasingInterpolator(Ease.CIRC_IN)
fadeAnimator.interpolator = EasingInterpolator(Ease.QUINT_IN)
}
if(animateIn) {
val scaleStartDelay = translateDuration - scaleDuration
val scaleStartValue = scaleValues[0]
middleButtonImageView.scaleX = scaleStartValue
middleButtonImageView.scaleY = scaleStartValue
scaleXAnimator.startDelay = scaleStartDelay
scaleYAnimator.startDelay = scaleStartDelay
}
val animateSet = AnimatorSet()
if(animateIn) {
animateSet.startDelay = translateDuration
}
animateSet.playTogether(translateAnimator, scaleXAnimator, scaleYAnimator)
return animateSet
}
Edit 2
Here is a video of how the animation looks on Android:
https://youtu.be/IKAB9A9qHic
So I've been working on my own solution using CADisplayLink. This is how the documentation describes CADisplayLink:
CADisplayLink is a timer object that allows your application to
synchronize its drawing to the refresh rate of the display.
It basically provides a callback when to perform drawing code (so you can run your animation smoothly).
I am not going to explain everything during this answer, because it's going to be a lot of code and most of it should be clear. If something is unclear or you have a question you can comment below this answer.
This solution gives complete freedom on animations and provides the ability to coordinate them. I looked a lot to the Animator class on Android and wanted a similar syntax so we can easily port the animations from Android to iOS or the other way around. I've tested it for a few days now and removed some quirks as well. But enough talking, let's see some code!
This is the Animator class, which is the base structure for the animation classes:
class Animator {
internal var displayLink: CADisplayLink? = nil
internal var startTime: Double = 0.0
var hasStarted: Bool = false
var hasStartedAnimating: Bool = false
var hasFinished: Bool = false
var isManaged: Bool = false
var isCancelled: Bool = false
var onAnimationStart: () -> Void = {}
var onAnimationEnd: () -> Void = {}
var onAnimationUpdate: () -> Void = {}
var onAnimationCancelled: () -> Void = {}
public func start() {
hasStarted = true
startTime = CACurrentMediaTime()
if(!isManaged) {
startDisplayLink()
}
}
internal func startDisplayLink() {
stopDisplayLink() // make sure to stop a previous running display link
displayLink = CADisplayLink(target: self, selector: #selector(animationTick))
displayLink?.add(to: .main, forMode: .commonModes)
}
internal func stopDisplayLink() {
displayLink?.invalidate()
displayLink = nil
}
#objc internal func animationTick() {
}
public func cancel() {
isCancelled = true
onAnimationCancelled()
if(!isManaged) {
animationTick()
}
}
}
It contains all vitals like starting up the CADisplayLink, providing ability to stop CADisplayLink (when the animation is done), boolean values which indicates the state and some callbacks. You'll also notice the isManaged boolean. This boolean is when an Animator is controlled by a group. If it is, the group will provide the animation ticks and this class shouldn't start the CADisplayLink.
Next up is the ValueAnimator:
class ValueAnimator : Animator {
public internal(set) var progress: Double = 0.0
public internal(set) var interpolatedProgress: Double = 0.0
var duration: Double = 0.3
var delay: Double = 0
var interpolator: Interpolator = EasingInterpolator(ease: .LINEAR)
override func animationTick() {
// In case this gets called after we finished
if(hasFinished) {
return
}
let elapsed: Double = (isCancelled) ? self.duration : CACurrentMediaTime() - startTime - delay
if(elapsed < 0) {
return
}
if(!hasStartedAnimating) {
hasStartedAnimating = true
onAnimationStart()
}
if(duration <= 0) {
progress = 1.0
} else {
progress = min(elapsed / duration, 1.0)
}
interpolatedProgress = interpolator.interpolate(elapsedTimeRate: progress)
updateAnimationValues()
onAnimationUpdate()
if(elapsed >= duration) {
endAnimation()
}
}
private func endAnimation() {
hasFinished = true
if(!isManaged) {
stopDisplayLink()
}
onAnimationEnd()
}
internal func updateAnimationValues() {
}
}
This class is the base class for all value animators. But it could also be used to do the animation on your own if you wish to do the calculations yourself. You'll probably noticed the Interpolator and interpolatedProgress here. The Interpolator class will be shown in a bit. This class provides the easing of the animation. This is where interpolatedProgress comes in. progress is just the linear progress from 0.0 to 1.0, but interpolatedProgress could have a different value for the easing. For example when progress has the value 0.2, interpolatedProgress might already have 0.4 based on what easing you'll use. Also make sure to use interpolatedProgress to calculate the right value. An example and the first subclass of ValueAnimator is below.
Below is the CGFloatValueAnimator which, as the name would suggest, animates CGFloat values:
class CGFloatValueAnimator : ValueAnimator {
private let startValue: CGFloat
private let endValue: CGFloat
public private(set) var animatedValue: CGFloat
init(startValue: CGFloat, endValue: CGFloat) {
self.startValue = startValue
self.endValue = endValue
self.animatedValue = startValue
}
override func updateAnimationValues() {
animatedValue = startValue + CGFloat(Double(endValue - startValue) * interpolatedProgress)
}
}
This is an example of how to subclass ValueAnimator and you can make many more like this if you need others like doubles or integers for example. You just provide a start and end value and the Animator calculates based on the interpolatedProgress what the current animatedValue is. You can use this animatedValue to update your view. I'll show an example at the end.
Because I mentioned Interpolator a couple times already, we'll continue to the Interpolator now:
protocol Interpolator {
func interpolate(elapsedTimeRate: Double) -> Double
}
It's just a protocol which you can implement yourself. I'll show you a part of the EasingInterpolator class I use myself. I can provide more if someone needs it.
class EasingInterpolator : Interpolator {
private let ease: Ease
init(ease: Ease) {
self.ease = ease
}
func interpolate(elapsedTimeRate: Double) -> Double {
switch (ease) {
case Ease.LINEAR:
return elapsedTimeRate
case Ease.SINE_IN:
return (1.0 - cos(elapsedTimeRate * Double.pi / 2.0))
case Ease.SINE_OUT:
return sin(elapsedTimeRate * Double.pi / 2.0)
case Ease.SINE_IN_OUT:
return (-0.5 * (cos(Double.pi * elapsedTimeRate) - 1.0))
case Ease.CIRC_IN:
return -(sqrt(1.0 - elapsedTimeRate * elapsedTimeRate) - 1.0)
case Ease.CIRC_OUT:
let newElapsedTimeRate = elapsedTimeRate - 1
return sqrt(1.0 - newElapsedTimeRate * newElapsedTimeRate)
case Ease.CIRC_IN_OUT:
var newElapsedTimeRate = elapsedTimeRate * 2.0
if (newElapsedTimeRate < 1.0) {
return (-0.5 * (sqrt(1.0 - newElapsedTimeRate * newElapsedTimeRate) - 1.0))
}
newElapsedTimeRate -= 2.0
return (0.5 * (sqrt(1 - newElapsedTimeRate * newElapsedTimeRate) + 1.0))
default:
return elapsedTimeRate
}
}
}
These are just a few examples of the calculations for specific easings. I actually ported all easings made for Android located here: https://github.com/MasayukiSuda/EasingInterpolator.
Before I show an example I have one more class to show. Which is the class that allows grouping of animators:
class AnimatorSet : Animator {
private var animators: [Animator] = []
var delay: Double = 0
var playSequential: Bool = false
override func start() {
super.start()
}
override func animationTick() {
// In case this gets called after we finished
if(hasFinished) {
return
}
let elapsed = CACurrentMediaTime() - startTime - delay
if(elapsed < 0 && !isCancelled) {
return
}
if(!hasStartedAnimating) {
hasStartedAnimating = true
onAnimationStart()
}
var finishedNumber = 0
for animator in animators {
if(!animator.hasStarted) {
animator.start()
}
animator.animationTick()
if(animator.hasFinished) {
finishedNumber += 1
} else {
if(playSequential) {
break
}
}
}
if(finishedNumber >= animators.count) {
endAnimation()
}
}
private func endAnimation() {
hasFinished = true
if(!isManaged) {
stopDisplayLink()
}
onAnimationEnd()
}
public func addAnimator(_ animator: Animator) {
animator.isManaged = true
animators.append(animator)
}
public func addAnimators(_ animators: [Animator]) {
for animator in animators {
animator.isManaged = true
self.animators.append(animator)
}
}
override func cancel() {
for animator in animators {
animator.cancel()
}
super.cancel()
}
}
As you can see, here is where I set the isManaged boolean. You can put multiple animators you make inside this class to coordinate them. And because this class also extends Animator you can also put in another AnimatorSet or multiple. By default it runs all the animations simultaneously, but if the playSequential is set to true, it will run all animations in order.
Time for a demo:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let animView = UIView()
animView.backgroundColor = UIColor.yellow
self.view.addSubview(animView)
animView.snp.makeConstraints { maker in
maker.width.height.equalTo(100)
maker.center.equalTo(self.view)
}
let translateAnimator = CGFloatValueAnimator(startValue: 0, endValue: 100)
translateAnimator.delay = 1.0
translateAnimator.duration = 1.0
translateAnimator.interpolator = EasingInterpolator(ease: .CIRC_IN_OUT)
translateAnimator.onAnimationStart = {
animView.backgroundColor = UIColor.blue
}
translateAnimator.onAnimationEnd = {
animView.backgroundColor = UIColor.green
}
translateAnimator.onAnimationUpdate = {
animView.transform.tx = translateAnimator.animatedValue
}
let alphaAnimator = CGFloatValueAnimator(startValue: animView.alpha, endValue: 0)
alphaAnimator.delay = 1.0
alphaAnimator.duration = 1.0
alphaAnimator.interpolator = EasingInterpolator(ease: .CIRC_IN_OUT)
alphaAnimator.onAnimationUpdate = {
animView.alpha = alphaAnimator.animatedValue
}
let animatorSet = AnimatorSet()
// animatorSet.playSequential = true // Uncomment this to play animations in order
animatorSet.addAnimator(translateAnimator)
animatorSet.addAnimator(alphaAnimator)
animatorSet.start()
}
}
I think most of this will speak for itself. I created a view which translates the x and fades out. For each animation you implement the onAnimationUpdate callback to alter values used in the view, like in this case the translation x and alpha.
Note: In contradiction to Android, the duration and delay are in seconds here instead of milliseconds.
We are working with this code right now and it works great! I already wrote some animation stuff in our Android app. I could easily port the animation to iOS with some minimal rewriting and the animation works exactly the same! I could copy the code written in my question, changed the Kotlin code to Swift, applied the onAnimationUpdate, changed the duration and delays to seconds and the animation worked like a charm.
I want to release this as an open source library, but I have not yet done this. I'll update this answer when I released it.
If you have any question about the code or how it works, feel free to ask.
Here is a start on the animation I think you are looking for. If you do not like the timing of the slides then you could switch out the UIView.animate with .curveEaseInOut for CAKeyframeAnimation where you could control each frame more granularly. You would want a CAKeyFrameAnimation for each view you are animating.
This is a playground and you can copy and paste it into an empty playground to see it in action.
import UIKit
import Foundation
import PlaygroundSupport
class ViewController: UIViewController {
let bottomBar = UIView()
let orangeButton = UIButton(frame: CGRect(x: 0, y: 10, width: 75, height: 75))
let yellow = UIView(frame: CGRect(x: 20, y: 20, width: 35, height: 35))
let magenta = UIView(frame: CGRect(x: 80, y: 30, width: 15, height: 15))
let cyan = UIView(frame: CGRect(x: 50, y: 20, width: 35, height: 35))
let brown = UIView(frame: CGRect(x: 150, y: 30, width:
15, height: 15))
let leftBox = UIView(frame: CGRect(x: 15, y: 10, width: 125, height: 75))
func setup() {
let reset = UIButton(frame: CGRect(x: 0, y: 0, width: 50, height: 50))
reset.backgroundColor = .white
reset.addTarget(self, action: #selector(resetAnimation), for: .touchUpInside)
self.view.addSubview(reset)
bottomBar.frame = CGRect(x: 0, y: self.view.frame.size.height - 100, width: self.view.frame.size.width, height: 100)
bottomBar.backgroundColor = .purple
self.view.addSubview(bottomBar)
orangeButton.backgroundColor = .orange
orangeButton.center.x = bottomBar.frame.size.width / 2
orangeButton.addTarget(self, action: #selector(orangeTapped(sender:)), for: .touchUpInside)
orangeButton.clipsToBounds = true
bottomBar.addSubview(orangeButton)
yellow.backgroundColor = .yellow
orangeButton.addSubview(yellow)
magenta.backgroundColor = .magenta
magenta.alpha = 0
orangeButton.addSubview(magenta)
// Left box is an invisible bounding box to get the effect that the view appeared from nowhere
// Clips to bounds so you cannot see the view when it has not been animated
// Try setting to false
leftBox.clipsToBounds = true
bottomBar.addSubview(leftBox)
cyan.backgroundColor = .cyan
leftBox.addSubview(cyan)
brown.backgroundColor = .brown
brown.alpha = 0
leftBox.addSubview(brown)
}
#objc func orangeTapped(sender: UIButton) {
// Perform animation
UIView.animate(withDuration: 0.2, delay: 0, options: .curveEaseInOut, animations: {
self.yellow.frame = CGRect(x: -20, y: 30, width: 15, height: 15)
self.yellow.alpha = 0
self.magenta.frame = CGRect(x: 20, y: 20, width: 35, height: 35)
self.magenta.alpha = 1
self.cyan.frame = CGRect(x: -150, y: 30, width: 15, height: 15)
self.cyan.alpha = 0
self.brown.frame = CGRect(x: 50, y: 20, width: 35, height: 35)
self.brown.alpha = 1
}, completion: nil)
}
#objc func resetAnimation() {
// Reset the animation back to the start
yellow.frame = CGRect(x: 20, y: 20, width: 35, height: 35)
yellow.alpha = 1
magenta.frame = CGRect(x: 80, y: 30, width: 15, height: 15)
magenta.alpha = 0
cyan.frame = CGRect(x: 50, y: 20, width: 35, height: 35)
cyan.alpha = 1
brown.frame = CGRect(x: 150, y: 30, width: 15, height: 15)
brown.alpha = 0
}
}
let viewController = ViewController()
viewController.view.frame = CGRect(x: 0, y: 0, width: 375, height: 667)
viewController.view.backgroundColor = .blue
viewController.setup()
PlaygroundPage.current.liveView = viewController

how to insert image in the left AND right to textfield using #IBDesignable in swift? [duplicate]

This question already has an answer here:
how to make designable textfield code class in swift
(1 answer)
Closed 5 years ago.
I am a beginner, I am trying to make a password textfield with two image, one on the left side (lock image) and another one on the right side to reveal the password like this one
but my senior ordered me to make it in the #IBDesignable class of text field so the main code stay clear from UI. i find something similar thread about this in here how to make designable textfield code class in swift
but it seems that the code is for left image only or right image only, i need both of them (lock and eyes symbol) appear in the textfield. I have tried to modify the code there, but i can't achieve what i need. really need your help, maybe you have done this in your previous project :)
import UIKit
#IBDesignable
class DesignableTextField: UITextField {
#IBInspectable var rightImage : UIImage? {
didSet {
updateRightView()
}
}
#IBInspectable var rightPadding : CGFloat = 0 {
didSet {
updateRightView()
}
}
#IBInspectable var leftImage : UIImage? {
didSet {
updateView()
}
}
#IBInspectable var leftPadding : CGFloat = 0 {
didSet {
updateView()
}
}
#IBInspectable var cornerRadiusOfField : CGFloat = 0 {
didSet {
layer.cornerRadius = cornerRadiusOfField
}
}
func updateView() {
if let image = leftImage {
leftViewMode = .always
// assigning image
let imageView = UIImageView(frame: CGRect(x: leftPadding, y: 0, width: 20, height: 20))
imageView.image = image
var width = leftPadding + 20
if borderStyle == UITextBorderStyle.none || borderStyle == UITextBorderStyle.line {
width += 5
}
let view = UIView(frame: CGRect(x: 0, y: 0, width: width, height: 20)) // has 5 point higher in width in imageView
view.addSubview(imageView)
leftView = view
} else {
// image is nill
leftViewMode = .never
}
}
func updateRightView() {
if let image = rightImage {
rightViewMode = .always
// assigning image
let imageView = UIImageView(frame: CGRect(x: rightPadding, y: 0, width: 20, height: 20))
imageView.image = image
var width = rightPadding - 20
if borderStyle == UITextBorderStyle.none || borderStyle == UITextBorderStyle.line {
width -= 5
}
let view = UIView(frame: CGRect(x: 0, y: 0, width: width, height: 20)) // has 5 point higher in width in imageView
view.addSubview(imageView)
rightView = view
} else {
// image is nill
rightViewMode = .never
}
}
}
Check below code:
#IBInspectable var leftSideImage:UIImage = UIImage() {
didSet {
leftView = UIImageView.init(image: leftSideImage)
leftViewMode = .always
}
}
#IBInspectable var rightSideImage:UIImage = UIImage() {
didSet {
rightView = UIImageView.init(image: rightSideImage)
rightViewMode = .always
}
}
Output:
An alternate way you can do, In storyboard add a button and add constraints as follows
Button trailing edge = textfield trailing edge
Button (vertical) centre = textfield (vertical) centre
give fix height and width to button
same you can do in right side.
Button leading edge = textfield leading edge
[]_______________[]

Swift 3 - setting a property on an array of views

I was previously able to clean up my code by adding multiple views (UIImageViews, UILabels, and UIButtons) to an array and then iterating through the array to make a property change like this:
var hideViews = [imageView1, imageView2, label1, button1, button2]
for eachView in hideViews {
eachView.isHidden = true
}
which then became in another version of Swift:
var hideViews = [imageView1, imageView2, label1, button1, button2] as [Any]
for eachView in hideViews {
(eachView as AnyObject).isHidden = true
}
I was also able to use this to move several views at once:
for view in viewsToMove {
(view as AnyObject).frame = CGRect(x: view.frame.origin.x - 30, y: view.frame.origin.y, width: view.frame.width, height: view.frame.height)
}
I am now getting the errors:
Cannot assign to immutable expression of type 'Bool!'
Cannot assign to immutable expression of type 'CGRect!'
Does anybody know what I'm missing here, in order to do this in Swift 3?
Thanks!
Given an array of UIView
let hideViews: [UIView] = ...
You can hide each view
hideViews.forEach { $0.isHidden = true }
move each view 30 points to the left
hideViews.forEach { $0.frame.origin.x -= 30 }
or both
hideViews.forEach {
$0.isHidden = true
$0.frame.origin.x -= 30
}
isHidden and frame are properties of UIView class so you should not cast them to AnyObject if you want to update properties that belong to them. Just do:
let views: [UIView] = [imageView1, imageView2, label1, button1, button2]
for view in views {
view.isHidden = true
view.frame = CGRect(x: ..., y: ...)
}
You don't need to force cast to Any or AnyObject.
This work For Swift3:
let v = UIView()
let i = UIImageView()
let l = UILabel()
let b = UIButton()
// Swift auto infers array type, this is equal to write
// let views: [UIView] = [v, i, l, b]
let views = [v, i, l, b]
views.forEach {
$0.isHidden = true
}
//or
for view in views {
view.frame = CGRect.zero
}

How come I can't access the methods of my subview?

In my ViewDidLoad:
let spiralDimension = CGFloat(ScreenWidth! * 0.10)
let spiralName = "spiral.png"
let spiralImage = UIImage(named: spiralName)
spiralView = UIImageView(image: spiralImage!)
spiralView!.frame = CGRect(x: ScreenWidth! / 2 - spiralDimension/2, y: (ScreenHeight!-TabBarHeight!) / 2 - spiralDimension/2 , width: spiralDimension, height: spiralDimension)
spiralView!.tintColor = UIColor.redColor()
self.view.addSubview(spiralView!) //works!!!
Somewhere later...
func fadeBackground(){
UIView.animateWithDuration(self.fadeTime, delay: 0, options: UIViewAnimationOptions.AllowUserInteraction, animations: {
var randomIndex = Int(arc4random_uniform(UInt32(CONSTANTS.MainColorScheme.count)))
var subviews = self.view.subviews
for v in subviews{
if v.isKindOfClass(UIImageView){
println(v) //correctly prints my spiral!
v.tintColor = CONSTANTS.MainColorScheme[randomIndex] //can't do it. XCode won't even auto-complete
}
}
}) { (stuff Bool) -> Void in
}
}
I can't assign tintColor to v, even though it prints my class correctly. Can't build successfully.
subviews returns an array of AnyObject. Thus, you need to cast v in order to set tintColor.
Try:
for v in subviews{
if let v = v as? UIImageView {
println(v)
v.tintColor = CONSTANTS.MainColorScheme[randomIndex]
}
}
You have to typecast it.
if v.isKindOfClass(UIImageView){
let iv = v as! UIImageView
v.tintColor = ...
The problem is that the subviews property is defined in iOS as a variable of type [AnyObject] so Swift doesn't know that your subviews are members of UIView, which has the property tintColor.

Resources