fix: StatefulButton appearance configuration

This commit is contained in:
Ivan Smolin 2023-07-17 18:51:41 +03:00
parent 094c0c40d8
commit 1a4c42fa46
5 changed files with 91 additions and 59 deletions

View File

@ -24,65 +24,21 @@ import TIUIKitCore
import UIKit
extension UIButton {
public func configureUIButton(appearance: BaseAppearance<some ViewLayout, some BaseContentLayout>) {
public func configureUIButton(appearance: BaseAppearance<some ViewLayout, some BaseContentLayout>,
for state: UIControl.State = .normal) {
appearance.textAttributes?
.configure(button: self,
with: titleLabel?.attributedText?.string ?? titleLabel?.text)
with: title(for: state),
for: state)
if #available(iOS 15, *) {
var config = configuration ?? .plain()
let contentInsets = appearance.contentLayout.contentInsets
let titleInsets = appearance.contentLayout.titleInsets
let imageInsets = appearance.contentLayout.imageInsets
let topContentInset = contentInsets.top + titleInsets.top
let bottomContentInset = contentInsets.bottom + titleInsets.bottom
let fullLeadingContentInset = contentInsets.left + titleInsets.left
let fullTrailingContentInset = contentInsets.right + titleInsets.right
let hasNonEmptyImage = [
config.image,
currentImage
]
.compactMap { $0 }
.first { !($0.size.width.isZero || $0.size.height.isZero) } != nil
if hasNonEmptyImage {
let leadingContentInset: CGFloat
let trailingContentInset: CGFloat
let imagePadding: CGFloat
switch config.imagePlacement {
case .leading:
leadingContentInset = contentInsets.left
trailingContentInset = fullTrailingContentInset
imagePadding = titleInsets.left + imageInsets.right
case .trailing:
leadingContentInset = fullLeadingContentInset
trailingContentInset = contentInsets.right
imagePadding = titleInsets.right + imageInsets.left
default:
leadingContentInset = fullLeadingContentInset
trailingContentInset = fullTrailingContentInset
imagePadding = .zero
}
config.contentInsets = NSDirectionalEdgeInsets(top: topContentInset,
leading: leadingContentInset,
bottom: bottomContentInset,
trailing: trailingContentInset)
config.imagePadding = imagePadding
} else {
config.contentInsets = NSDirectionalEdgeInsets(top: topContentInset,
leading: fullLeadingContentInset,
bottom: bottomContentInset,
trailing: fullTrailingContentInset)
}
configureInsets(in: &config,
contentInsets: appearance.contentLayout.contentInsets,
titleInsets: appearance.contentLayout.titleInsets,
imageInsets: appearance.contentLayout.imageInsets)
configuration = config
} else {
@ -93,4 +49,59 @@ extension UIButton {
super.configureUIView(appearance: appearance)
}
@available(iOS 15.0, *)
private func configureInsets(in config: inout UIButton.Configuration,
contentInsets: UIEdgeInsets,
titleInsets: UIEdgeInsets,
imageInsets: UIEdgeInsets) {
let topContentInset = contentInsets.top + titleInsets.top
let bottomContentInset = contentInsets.bottom + titleInsets.bottom
let fullLeadingContentInset = contentInsets.left + titleInsets.left
let fullTrailingContentInset = contentInsets.right + titleInsets.right
let hasNonEmptyImage = [
config.image,
currentImage
]
.compactMap { $0 }
.first { !($0.size.width.isZero || $0.size.height.isZero) } != nil
if hasNonEmptyImage {
let leadingContentInset: CGFloat
let trailingContentInset: CGFloat
let imagePadding: CGFloat
switch config.imagePlacement {
case .leading:
leadingContentInset = contentInsets.left
trailingContentInset = fullTrailingContentInset
imagePadding = titleInsets.left + imageInsets.right
case .trailing:
leadingContentInset = fullLeadingContentInset
trailingContentInset = contentInsets.right
imagePadding = titleInsets.right + imageInsets.left
default:
leadingContentInset = fullLeadingContentInset
trailingContentInset = fullTrailingContentInset
imagePadding = .zero
}
config.contentInsets = NSDirectionalEdgeInsets(top: topContentInset,
leading: leadingContentInset,
bottom: bottomContentInset,
trailing: trailingContentInset)
config.imagePadding = imagePadding
} else {
config.contentInsets = NSDirectionalEdgeInsets(top: topContentInset,
leading: fullLeadingContentInset,
bottom: bottomContentInset,
trailing: fullTrailingContentInset)
}
}
}

View File

@ -23,8 +23,8 @@
import TIUIKitCore
import UIKit
extension UIView {
public func configureUIView(appearance: BaseAppearance<some ViewLayout>) {
public extension UIView {
func configureUIView(appearance: BaseAppearance<some ViewLayout>) {
backgroundColor = appearance.backgroundColor
layer.masksToBounds = true
layer.maskedCorners = appearance.border.roundedCorners

View File

@ -25,11 +25,15 @@ import UIKit
public final class DefaultConfigurableStatefulButton: StatefulButton, ConfigurableView, AppearanceConfigurable {
private var appearance: Appearance = .defaultAppearance
// MARK: - ConfigurableView
public func configure(with viewModel: ViewModel) {
viewModel.willReuse(view: self)
stateViewModelMap = viewModel.stateViewModelMap
for (state, viewModel) in viewModel.stateViewModelMap {
setTitle(viewModel.title, for: state)
setImage(viewModel.image, for: state)
@ -38,12 +42,15 @@ public final class DefaultConfigurableStatefulButton: StatefulButton, Configurab
apply(state: viewModel.currentState)
configureStatefulButton(appearance: appearance)
viewModel.didCompleteConfiguration(of: self)
}
// MARK: - AppearanceConfigurable
public func configure(appearance: DefaultPositionAppearance) {
self.appearance = appearance
configureStatefulButton(appearance: appearance)
}
}

View File

@ -127,9 +127,9 @@ extension StatefulButton {
public func configureBaseStatefulButton(appearance: BaseAppearance<some ViewLayout, some BaseContentLayout>) {
onStateChanged = { [weak self] in
if let stateAppearance = appearance.stateAppearances[$0] {
self?.configureUIButton(appearance: stateAppearance)
self?.configureUIButton(appearance: stateAppearance, for: $0)
} else if $0 != .normal, let stateAppearance = appearance.stateAppearances[.normal] {
self?.configureUIButton(appearance: stateAppearance)
self?.configureUIButton(appearance: stateAppearance, for: .normal)
}
}

View File

@ -55,12 +55,12 @@ open class StatefulButton: BaseInitializableButton {
configureDefaultActivityIndicator()
}
updateAppearance(to: .loading)
activityIndicator?.startAnimating()
} else {
activityIndicator?.stopAnimating()
}
updateAppearance()
}
}
@ -83,6 +83,8 @@ open class StatefulButton: BaseInitializableButton {
var activityIndicatorShouldCenterInView = false
var stateViewModelMap: [State: BaseButtonViewModel] = [:]
var onStateChanged: ParameterClosure<State>?
// MARK: - Private properties
@ -103,6 +105,18 @@ open class StatefulButton: BaseInitializableButton {
super.setImage(image, for: state)
}
open override func title(for state: UIControl.State) -> String? {
stateViewModelMap[state]?.title ?? super.title(for: state)
}
open override func image(for state: UIControl.State) -> UIImage? {
stateViewModelMap[state]?.image ?? super.image(for: state)
}
open override func backgroundImage(for state: UIControl.State) -> UIImage? {
stateViewModelMap[state]?.backgroundImage ?? super.backgroundImage(for: state)
}
// MARK: - UIControl override
open override var state: UIControl.State {