Skip to content

Commit

Permalink
Merge branch 'main' into refacto/switch/space
Browse files Browse the repository at this point in the history
  • Loading branch information
robergro authored Oct 27, 2023
2 parents 7f7fec6 + 2b28332 commit 1321c0e
Show file tree
Hide file tree
Showing 14 changed files with 341 additions and 111 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
//
// UIView+ExecuteExtension.swift
// SparkCore
//
// Created by robin.lemaire on 26/10/2023.
// Copyright © 2023 Adevinta. All rights reserved.
//

import UIKit

enum UIExecuteAnimationType {
case unanimated
case animated(duration: TimeInterval)
}

extension UIView {

/// Execute a code with or without animation.
static func execute(
animationType: UIExecuteAnimationType,
instructions: @escaping () -> Void,
completion: ((Bool) -> Void)? = nil
) {
switch animationType {
case .unanimated:
instructions()
completion?(true)

case .animated(let duration):
UIView.animate(
withDuration: duration,
animations: instructions,
completion: completion)
}
}

/// Execute a code with or without transition animation.
static func execute(
with view: UIView,
animationType: UIExecuteAnimationType,
options: UIView.AnimationOptions = [],
instructions: @escaping () -> Void,
completion: ((Bool) -> Void)? = nil
) {
switch animationType {
case .unanimated:
instructions()
completion?(true)

case .animated(let duration):
UIView.transition(
with: view,
duration: duration,
options: options,
animations: instructions,
completion: completion)
}
}
}
43 changes: 25 additions & 18 deletions core/Sources/Components/Button/View/UIKit/ButtonUIView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public final class ButtonUIView: UIView {
// MARK: - Type alias

private typealias AccessibilityIdentifier = ButtonAccessibilityIdentifier
private typealias Constants = ButtonConstants
private typealias Animation = ButtonConstants.Animation

// MARK: - Components

Expand Down Expand Up @@ -213,6 +213,9 @@ public final class ButtonUIView: UIView {
}
}

/// Button modifications should be animated or not. **True** by default.
public var isAnimated: Bool = true

// MARK: - Internal Properties

internal let viewModel: ButtonViewModel
Expand Down Expand Up @@ -572,11 +575,14 @@ public final class ButtonUIView: UIView {
let horizontalSpacing = self._horizontalSpacing.wrappedValue
let horizontalPadding = self._horizontalPadding.wrappedValue

let animationDuration = self.firstContentStackViewAnimation ? 0 : Constants.Animation.slowDuration
if verticalSpacing != self.contentStackViewTopConstraint?.constant ||
horizontalSpacing != self.contentStackViewLeadingConstraint?.constant ||
horizontalPadding != self.contentStackView.spacing {
UIView.animate(withDuration: animationDuration) { [weak self] in

let isAnimated = self.isAnimated && !self.firstContentStackViewAnimation
let animationType: UIExecuteAnimationType = isAnimated ? .animated(duration: Animation.slowDuration) : .unanimated

UIView.execute(animationType: animationType) { [weak self] in
guard let self else { return }

self.firstContentStackViewAnimation = false
Expand Down Expand Up @@ -617,12 +623,12 @@ public final class ButtonUIView: UIView {

// Animate only if new alpha is different from current alpha
let alpha = state.opacity
if self.alpha != alpha {
UIView.animate(withDuration: Constants.Animation.slowDuration) { [weak self] in
self?.alpha = alpha
}
} else {
self.alpha = alpha

let isAnimated = self.isAnimated && self.alpha != alpha
let animationType: UIExecuteAnimationType = isAnimated ? .animated(duration: Animation.slowDuration) : .unanimated

UIView.execute(animationType: animationType) { [weak self] in
self?.alpha = alpha
}
}
// **
Expand All @@ -633,12 +639,11 @@ public final class ButtonUIView: UIView {
guard let self, let colors else { return }

// Background Color
if self.backgroundColor != colors.backgroundColor.uiColor {
UIView.animate(withDuration: Constants.Animation.fastDuration) { [weak self] in
self?.backgroundColor = colors.backgroundColor.uiColor
}
} else {
self.backgroundColor = colors.backgroundColor.uiColor
let isAnimated = self.isAnimated && self.backgroundColor != colors.backgroundColor.uiColor
let animationType: UIExecuteAnimationType = isAnimated ? .animated(duration: Animation.fastDuration) : .unanimated

UIView.execute(animationType: animationType) { [weak self] in
self?.backgroundColor = colors.backgroundColor.uiColor
}

// Border Color
Expand Down Expand Up @@ -709,8 +714,10 @@ public final class ButtonUIView: UIView {
self.iconImageView.image = content.iconImage?.leftValue

// Subviews positions and visibilities
let animationDuration = self.firstContentStackViewSubviewAnimation ? 0 : Constants.Animation.slowDuration
UIView.animate(withDuration: animationDuration) { [weak self] in
let isAnimated = self.isAnimated && !self.firstContentStackViewSubviewAnimation
let animationType: UIExecuteAnimationType = isAnimated ? .animated(duration: Animation.slowDuration) : .unanimated

UIView.execute(animationType: animationType) { [weak self] in
guard let self else { return }

self.firstContentStackViewSubviewAnimation = false
Expand Down Expand Up @@ -765,7 +772,7 @@ public final class ButtonUIView: UIView {
}

private func unpressedAction() {
DispatchQueue.main.asyncAfter(deadline: .now() + Constants.Animation.fastDuration, execute: { [weak self] in
DispatchQueue.main.asyncAfter(deadline: .now() + Animation.fastDuration, execute: { [weak self] in
self?.viewModel.unpressedAction()
})
}
Expand Down
4 changes: 4 additions & 0 deletions core/Sources/Components/Chip/View/UIKit/ChipUIView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -539,6 +539,10 @@ public final class ChipUIView: UIControl {
self.viewModel.isPressed = false
}
}

public func enableComponentUserInteraction(_ isEnabled: Bool) {
self.stackView.isUserInteractionEnabled = isEnabled
}
}

// MARK: - Label priorities
Expand Down
94 changes: 52 additions & 42 deletions core/Sources/Components/Switch/View/UIKit/SwitchUIView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -129,12 +129,13 @@ public final class SwitchUIView: UIView {
}
}

/// The value of the switch.
/// The value of the switch (retrieve and set without animation).
public var isOn: Bool {
get {
return self.viewModel.isOn
}
set {
self.isOnAnimated = false
self.viewModel.set(isOn: newValue)
}
}
Expand All @@ -159,12 +160,13 @@ public final class SwitchUIView: UIView {
}
}

/// The state of the switch: enabled or not.
/// The state of the switch: enabled or not (retrieve and set without animation). .
public var isEnabled: Bool {
get {
return self.viewModel.isEnabled
}
set {
self.isEnabledAnimated = false
self.viewModel.set(isEnabled: newValue)
}
}
Expand Down Expand Up @@ -250,6 +252,9 @@ public final class SwitchUIView: UIView {
@ScaledUIMetric private var toggleSpacing: CGFloat = Constants.ToggleSizes.padding
@ScaledUIMetric private var toggleDotSpacing: CGFloat = Constants.toggleDotImagePadding

private var isEnabledAnimated: Bool = false
private var isOnAnimated: Bool = false

private var subscriptions = Set<AnyCancellable>()

// MARK: - Initialization
Expand Down Expand Up @@ -605,6 +610,20 @@ public final class SwitchUIView: UIView {
self.textLabel.heightAnchor.constraint(greaterThanOrEqualTo: self.toggleView.heightAnchor).isActive = true
}

// MARK: - Setter

/// Sets the state of the switch to the on or off position, optionally animating the transition.
public func setOn(_ isOn: Bool, animated: Bool) {
self.isOnAnimated = animated
self.viewModel.set(isOn: isOn)
}

/// Sets the enable status of the switch, optionally animating the color.
public func setEnabled(_ isEnabled: Bool, animated: Bool) {
self.isEnabledAnimated = animated
self.viewModel.set(isEnabled: isEnabled)
}

// MARK: - Actions

@objc private func toggleTapGestureAction(_ sender: UITapGestureRecognizer) {
Expand Down Expand Up @@ -702,14 +721,12 @@ public final class SwitchUIView: UIView {
guard let self, let toggleOpacity else { return }

// Animate only if new alpha is different from current alpha
let animated = self.toggleView.alpha != toggleOpacity

if animated {
UIView.animate(withDuration: Constants.animationDuration) { [weak self] in
self?.toggleView.alpha = toggleOpacity
}
} else {
self.toggleView.alpha = toggleOpacity

let isAnimated = self.isEnabledAnimated && self.toggleView.alpha != toggleOpacity
let animationType: UIExecuteAnimationType = isAnimated ? .animated(duration: Constants.animationDuration) : .unanimated

UIView.execute(animationType: animationType) { [weak self] in
self?.toggleView.alpha = toggleOpacity
}
}
// **
Expand All @@ -720,14 +737,11 @@ public final class SwitchUIView: UIView {
guard let self, let colorToken else { return }

// Animate only if there is currently an color on View and if new color is different from current color
let animated = self.toggleView.backgroundColor != nil && self.toggleView.backgroundColor != colorToken.uiColor

if animated {
UIView.animate(withDuration: Constants.animationDuration) { [weak self] in
self?.toggleView.backgroundColor = colorToken.uiColor
}
} else {
self.toggleView.backgroundColor = colorToken.uiColor
let isAnimated = self.isOnAnimated && self.toggleView.backgroundColor != nil && self.toggleView.backgroundColor != colorToken.uiColor
let animationType: UIExecuteAnimationType = isAnimated ? .animated(duration: Constants.animationDuration) : .unanimated

UIView.execute(animationType: animationType) { [weak self] in
self?.toggleView.backgroundColor = colorToken.uiColor
}
}
self.viewModel.$toggleDotBackgroundColorToken.subscribe(in: &self.subscriptions) { [weak self] colorToken in
Expand All @@ -739,14 +753,11 @@ public final class SwitchUIView: UIView {
guard let self, let colorToken else { return }

// Animate only if there is currently an color on View and if new color is different from current color
let animated = self.toggleDotImageView.tintColor != nil && self.toggleDotImageView.tintColor != colorToken.uiColor

if animated {
UIView.animate(withDuration: Constants.animationDuration) { [weak self] in
self?.toggleDotImageView.tintColor = colorToken.uiColor
}
} else {
self.toggleDotImageView.tintColor = colorToken.uiColor
let isAnimated = self.isOnAnimated && self.toggleDotImageView.tintColor != nil && self.toggleDotImageView.tintColor != colorToken.uiColor
let animationType: UIExecuteAnimationType = isAnimated ? .animated(duration: Constants.animationDuration) : .unanimated

UIView.execute(animationType: animationType) { [weak self] in
self?.toggleDotImageView.tintColor = colorToken.uiColor
}
}
self.viewModel.$textForegroundColorToken.subscribe(in: &self.subscriptions) { [weak self] colorToken in
Expand Down Expand Up @@ -786,7 +797,9 @@ public final class SwitchUIView: UIView {
let currentUserInteraction = self.viewModel.isToggleInteractionEnabled ?? true
self.toggleView.isUserInteractionEnabled = false

UIView.animate(withDuration: Constants.animationDuration) { [weak self] in
let animationType: UIExecuteAnimationType = self.isOnAnimated ? .animated(duration: Constants.animationDuration) : .unanimated

UIView.execute(animationType: animationType) { [weak self] in
self?.toggleLeftSpaceView.isHidden = !showLeftSpace
self?.toggleRightSpaceView.isHidden = showLeftSpace
} completion: { [weak self] _ in
Expand All @@ -803,21 +816,18 @@ public final class SwitchUIView: UIView {
let image = toggleDotImagesState?.currentImage.leftValue

// Animate only if there is currently an image on ImageView and new image is exists
let animated = self.toggleDotImageView.image != nil && image != nil

if animated {
UIView.transition(
with: self.toggleDotImageView,
duration: Constants.animationDuration,
options: .transitionCrossDissolve,
animations: {
self.toggleDotImageView.image = image
},
completion: nil
)
} else {
self.toggleDotImageView.image = image
}
let isAnimated = self.isOnAnimated && self.toggleDotImageView.image != nil && image != nil
let animationType: UIExecuteAnimationType = isAnimated ? .animated(duration: Constants.animationDuration) : .unanimated

UIView.execute(
with: self.toggleDotImageView,
animationType: animationType,
options: .transitionCrossDissolve,
instructions: { [weak self] in
self?.toggleDotImageView.image = image
},
completion: nil
)
}

// Displayed Text
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,20 @@ final class TabUIViewSnapshotTests: UIKitComponentSnapshotTestCase {

// MARK: - Properties
let theme = SparkTheme.shared
let names = ["paperplane", "folder", "trash", "pencil", "eraser", "scribble", "lasso"]
let names = ["paperplane", "folder", "trash", "pencil", "scribble", "lasso"]
var badge: BadgeUIView!
var images: [UIImage]!

// MARK: - Setup
override func setUp() {
super.setUp()

self.images = names.map{ UIImage.init(systemName: $0)! }
self.images = names.map{
guard let image = UIImage.init(systemName: $0) else {
fatalError("No Image for \($0)")
}
return image
}
self.badge = BadgeUIView(theme: theme, intent: .danger, value: 99, isBorderVisible: false)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ struct ButtonComponentView: View {
@State private var alignment: ButtonAlignment = .leadingIcon
@State private var content: ButtonContentDefault = .text
@State private var isEnabled: CheckboxSelectionState = .selected
@State private var isAnimated: CheckboxSelectionState = .selected

@State private var shouldShowReverseBackgroundColor: Bool = false

Expand Down Expand Up @@ -89,6 +90,14 @@ struct ButtonComponentView: View {
state: .enabled,
selectionState: self.$isEnabled
)

CheckboxView(
text: "Is animated",
checkedImage: DemoIconography.shared.checkmark,
theme: self.theme,
state: .enabled,
selectionState: self.$isAnimated
)
},
integration: {
GeometryReader { geometry in
Expand All @@ -102,7 +111,8 @@ struct ButtonComponentView: View {
shape: self.$shape.wrappedValue,
alignment: self.$alignment.wrappedValue,
content: self.$content.wrappedValue,
isEnabled: self.$isEnabled.wrappedValue == .selected
isEnabled: self.$isEnabled.wrappedValue == .selected,
isAnimated: self.$isAnimated.wrappedValue == .selected
)
.frame(width: geometry.size.width, height: self.uiKitViewHeight, alignment: .center)
.padding(.horizontal, self.shouldShowReverseBackgroundColor ? 4 : 0)
Expand Down
Loading

0 comments on commit 1321c0e

Please sign in to comment.