LeadKit/TIUIElements/Sources/Views/ListItemView/BaseListItemView.swift

304 lines
14 KiB
Swift

//
// Copyright (c) 2023 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import TIUIKitCore
import UIKit
open class BaseListItemView<LeadingView: UIView,
MiddleView: UIView,
TrailingView: UIView>: BaseInitializableView {
public let leadingView = LeadingView()
public let middleView = MiddleView()
public let trailingView = TrailingView()
// MARK: - Constraints
public private(set) lazy var leadingViewTrailingToSuperviewConstraint: NSLayoutConstraint = {
leadingView.trailingAnchor.constraint(equalTo: trailingAnchor)
}()
public private(set) lazy var leadingToTrailingViewConstraint: NSLayoutConstraint = {
leadingView.trailingAnchor.constraint(equalTo: trailingView.leadingAnchor)
}()
public private(set) lazy var leadingViewConstraints: SubviewConstraints = {
let edgeConstraints = EdgeConstraints(leadingConstraint: leadingView.leadingAnchor.constraint(equalTo: leadingAnchor),
trailingConstraint: middleViewLeadingToLeadingViewConstraint,
topConstraint: leadingView.topAnchor.constraint(equalTo: topAnchor),
bottomConstraint: leadingView.bottomAnchor.constraint(equalTo: bottomAnchor))
let centerXConstraint = leadingView.centerXAnchor.constraint(equalTo: centerXAnchor)
let centerYConstraint = leadingView.centerYAnchor.constraint(equalTo: centerYAnchor)
let centerConstraints = CenterConstraints(centerXConstraint: centerXConstraint,
centerYConstraint: centerYConstraint)
let sizeConstraints = SizeConstraints(widthConstraint: leadingView.widthAnchor.constraint(equalToConstant: .zero),
heightConstraint: leadingView.heightAnchor.constraint(equalToConstant: .zero))
return SubviewConstraints(edgeConstraints: edgeConstraints,
centerConstraints: centerConstraints,
sizeConstraints: sizeConstraints)
}()
public private(set) lazy var middleViewLeadingToLeadingViewConstraint: NSLayoutConstraint = {
middleView.leadingAnchor.constraint(equalTo: leadingView.trailingAnchor)
}()
public private(set) lazy var middleToTrailingViewConstraint: NSLayoutConstraint = {
middleView.trailingAnchor.constraint(equalTo: trailingView.leadingAnchor)
}()
public private(set) lazy var middleViewLeadingToSuperViewConstraint: NSLayoutConstraint = {
middleView.leadingAnchor.constraint(equalTo: leadingAnchor)
}()
public private(set) lazy var middleViewTrailingToSuperViewConstraint: NSLayoutConstraint = {
middleView.trailingAnchor.constraint(equalTo: trailingAnchor)
}()
public private(set) lazy var middleViewConstraints: SubviewConstraints = {
let edgeConstraints = EdgeConstraints(leadingConstraint: middleViewLeadingToLeadingViewConstraint,
trailingConstraint: middleToTrailingViewConstraint,
topConstraint: middleView.topAnchor.constraint(equalTo: topAnchor),
bottomConstraint: middleView.bottomAnchor.constraint(equalTo: bottomAnchor))
let centerXConstraint = middleView.centerXAnchor.constraint(equalTo: centerXAnchor)
let centerYConstraint = middleView.centerYAnchor.constraint(equalTo: centerYAnchor)
let centerConstraints = CenterConstraints(centerXConstraint: centerXConstraint,
centerYConstraint: centerYConstraint)
let sizeConstraints = SizeConstraints(widthConstraint: middleView.widthAnchor.constraint(equalToConstant: .zero),
heightConstraint: middleView.heightAnchor.constraint(equalToConstant: .zero))
return SubviewConstraints(edgeConstraints: edgeConstraints,
centerConstraints: centerConstraints,
sizeConstraints: sizeConstraints)
}()
public private(set) lazy var trailingViewLeadingToSuperviewConstraint: NSLayoutConstraint = {
trailingView.leadingAnchor.constraint(equalTo: leadingAnchor)
}()
public private(set) lazy var trailingViewConstraints: SubviewConstraints = {
let trailingConstraint = trailingView.trailingAnchor.constraint(equalTo: trailingAnchor)
let edgeConstraints = EdgeConstraints(leadingConstraint: middleToTrailingViewConstraint,
trailingConstraint: trailingConstraint,
topConstraint: trailingView.topAnchor.constraint(equalTo: topAnchor),
bottomConstraint: trailingView.bottomAnchor.constraint(equalTo: bottomAnchor))
let centerXConstraint = middleView.centerXAnchor.constraint(equalTo: centerXAnchor)
let centerYConstraint = trailingView.centerYAnchor.constraint(equalTo: centerYAnchor)
let centerConstraints = CenterConstraints(centerXConstraint: centerXConstraint,
centerYConstraint: centerYConstraint)
let sizeConstraints = SizeConstraints(widthConstraint: trailingView.widthAnchor.constraint(equalToConstant: .zero),
heightConstraint: trailingView.heightAnchor.constraint(equalToConstant: .zero))
return SubviewConstraints(edgeConstraints: edgeConstraints,
centerConstraints: centerConstraints,
sizeConstraints: sizeConstraints)
}()
// MARK: - Public Properties
open var isLeadingViewHidden: Bool {
leadingView.isHidden
}
open var isMiddleViewHidden: Bool {
middleView.isHidden
}
open var isTrailingViewHidden: Bool {
trailingView.isHidden
}
// MARK: - BaseInitializableView
open override func addViews() {
super.addViews()
addSubviews(leadingView, middleView, trailingView)
}
open override func configureLayout() {
super.configureLayout()
[leadingView, middleView, trailingView]
.forEach { $0.translatesAutoresizingMaskIntoConstraints = false }
}
// MARK: - Public methods
public func configureBaseListItem(appearance: BaseListItemAppearance<some WrappedViewAppearance,
some WrappedViewAppearance,
some WrappedViewAppearance>) {
configureUIView(appearance: appearance)
let leadingViewTrailingSpacing = updateLeadingConstaints(leadingViewLayout: appearance.leadingViewAppearance.layout,
middleViewLayout: appearance.middleViewAppearance.layout,
trailingViewLayout: appearance.trailingAppearance.layout)
let trailingViewLeadingSpacing = updateTrailingConstraints(trailingViewLayout: appearance.trailingAppearance.layout,
middleViewLayout: appearance.middleViewAppearance.layout,
leadingViewLayout: appearance.leadingViewAppearance.layout)
updateMiddleConstraints(middleViewLayout: appearance.middleViewAppearance.layout,
leadingViewTrailingSpacing: leadingViewTrailingSpacing,
trailingViewLeadingSpacing: trailingViewLeadingSpacing)
}
// MARK: - Private methdos
private func updateLeadingConstaints(leadingViewLayout: WrappedViewLayout,
middleViewLayout: WrappedViewLayout,
trailingViewLayout: WrappedViewLayout) -> CGFloat {
switch (isLeadingViewHidden, isMiddleViewHidden) {
case (true, true):
leadingViewConstraints.deactivate()
return UIEdgeInsets.nan.right
case (false, false):
leadingViewConstraints.edgeConstraints.trailingConstraint.isActive = false
leadingViewConstraints.edgeConstraints.trailingConstraint = middleViewLeadingToLeadingViewConstraint
leadingViewConstraints.update(from: leadingViewLayout)
return middleViewLayout.insets.add(\.left,
to: \.right,
of: leadingViewLayout.insets)
case (true, false):
leadingViewConstraints.deactivate()
return middleViewLayout.insets.left
case (false, true):
leadingViewConstraints.edgeConstraints.trailingConstraint.isActive = false
defer {
leadingViewConstraints.update(from: leadingViewLayout)
}
if isTrailingViewHidden {
leadingViewConstraints.edgeConstraints.trailingConstraint = leadingViewTrailingToSuperviewConstraint
return -leadingViewLayout.insets.right
} else {
leadingViewConstraints.edgeConstraints.trailingConstraint = leadingToTrailingViewConstraint
return leadingViewLayout.insets.add(\.right,
to: \.right,
of: trailingViewLayout.insets)
}
}
}
private func updateMiddleConstraints(middleViewLayout: WrappedViewLayout,
leadingViewTrailingSpacing: CGFloat,
trailingViewLeadingSpacing: CGFloat) {
guard !isMiddleViewHidden else {
middleViewConstraints.deactivate()
return
}
middleViewConstraints.edgeConstraints.leadingConstraint.isActive = false
if isLeadingViewHidden {
middleViewConstraints.edgeConstraints.leadingConstraint = middleViewLeadingToSuperViewConstraint
} else {
middleViewConstraints.edgeConstraints.leadingConstraint = middleViewLeadingToLeadingViewConstraint
}
middleViewConstraints.edgeConstraints.trailingConstraint.isActive = false
if isTrailingViewHidden {
middleViewConstraints.edgeConstraints.trailingConstraint = middleViewTrailingToSuperViewConstraint
} else {
middleViewConstraints.edgeConstraints.trailingConstraint = middleToTrailingViewConstraint
}
middleViewConstraints.update(from: middleViewLayout)
middleViewConstraints.edgeConstraints.leadingConstraint
.setActiveConstantOrDeactivate(constant: leadingViewTrailingSpacing)
middleViewConstraints.edgeConstraints.trailingConstraint
.setActiveConstantOrDeactivate(constant: trailingViewLeadingSpacing)
}
private func updateTrailingConstraints(trailingViewLayout: WrappedViewLayout,
middleViewLayout: WrappedViewLayout,
leadingViewLayout: WrappedViewLayout) -> CGFloat {
switch (isMiddleViewHidden, isTrailingViewHidden) {
case (true, true):
trailingViewConstraints.deactivate()
return UIEdgeInsets.nan.left
case (true, false):
trailingViewConstraints.edgeConstraints.leadingConstraint.isActive = false
defer {
trailingViewConstraints.update(from: trailingViewLayout)
}
if isLeadingViewHidden {
trailingViewConstraints.edgeConstraints.leadingConstraint = trailingViewLeadingToSuperviewConstraint
return trailingViewLayout.insets.left
} else {
trailingViewConstraints.edgeConstraints.leadingConstraint = middleToTrailingViewConstraint
return trailingViewLayout.insets.add(\.left,
to: \.right,
of: middleViewLayout.insets)
}
case (false, true):
trailingViewConstraints.deactivate()
return -middleViewLayout.insets.right
case (false, false):
trailingViewConstraints.edgeConstraints.leadingConstraint.isActive = false
trailingViewConstraints.edgeConstraints.leadingConstraint = middleToTrailingViewConstraint
trailingViewConstraints.update(from: trailingViewLayout)
return trailingViewLayout.insets.add(\.right,
to: \.left,
of: middleViewLayout.insets)
}
}
}