Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 20 additions & 13 deletions Sources/StackKit/HStackView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -78,9 +78,17 @@ open class HStackView: UIView {
self.isHidden = effectiveSubviews.isEmpty
}

private func tryResizeStackView() {
subviews.forEach { fitSize in
fitSize._fitSize(with: fitSize.stackKitFitType)
}
}

open override func layoutSubviews() {
super.layoutSubviews()

tryResizeStackView()

switch alignment {
case .top:
effectiveSubviews.forEach { $0.frame.origin.y = 0 }
Expand Down Expand Up @@ -123,7 +131,8 @@ open class HStackView: UIView {
}

open override func sizeThatFits(_ size: CGSize) -> CGSize {
layoutSubviews()
setNeedsLayout()
layoutIfNeeded()

var _size = size
if size.width == CGFloat.greatestFiniteMagnitude || size.width == 0 {
Expand Down Expand Up @@ -174,7 +183,11 @@ extension HStackView {
private func autoSpacing() -> CGFloat {
let unspacerViews = viewsWithoutSpacer()
let spacersCount = spacerViews().map({ isSpacerBetweenViews($0) }).filter({ $0 }).count
return (frame.width - viewsWidth() - spacerSpecifyLength()) / CGFloat(unspacerViews.count - spacersCount - 1)
let number = unspacerViews.count - spacersCount - 1
if number <= 0 {
return 0
}
return (frame.width - viewsWidth() - spacerSpecifyLength()) / CGFloat(number)
}

private func viewsWidth() -> CGFloat {
Expand Down Expand Up @@ -254,19 +267,13 @@ extension HStackView {
return false
}

var isPreviousView = false
var isNextView = false

let previous = index - 1
if previous > 0, previous < effectiveSubviews.count - 1 {
isPreviousView = true
guard effectiveSubviews.count >= 3 else {
return false
}

let next = index + 1
if next < effectiveSubviews.count - 1 {
isNextView = true
}
return isPreviousView && isNextView
let start: Int = 1
let end: Int = effectiveSubviews.count - 2
return (start ... end).contains(index)
}

private func fillSpecifySpacer() {
Expand Down
38 changes: 38 additions & 0 deletions Sources/StackKit/Runtime.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
//
// File.swift
//
//
// Created by i on 2022/8/11.
//

import UIKit

struct Runtime {
init() { }
}

extension Runtime {

static func getProperty(_ object: Any, key: UnsafeRawPointer) -> Any? {
objc_getAssociatedObject(object, key)
}

static func setProperty(_ object: Any, key: UnsafeRawPointer, value: Any?, policy: objc_AssociationPolicy = .OBJC_ASSOCIATION_RETAIN_NONATOMIC) {
objc_setAssociatedObject(object, key, value, policy)
}

static func getCGFloatProperty(_ object: Any, key: UnsafeRawPointer) -> CGFloat? {
guard let value = getProperty(object, key: key) as? NSNumber else {
return nil
}
return CGFloat(truncating: value)
}

static func setCGFloatProperty(_ object: Any, key: UnsafeRawPointer, _ value: CGFloat?) {
guard let v = value else {
objc_setAssociatedObject(object, key, nil, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
return
}
objc_setAssociatedObject(object, key, NSNumber(value: Double(v)), .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
20 changes: 10 additions & 10 deletions Sources/StackKit/Spacer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ class SpacerView: UIView, _Spacer {
var min: CGFloat = .leastNonzeroMagnitude
var max: CGFloat = .greatestFiniteMagnitude

var setLength: CGFloat = -1
var setLength: CGFloat = 0

required init(length: CGFloat, min: CGFloat, max: CGFloat) {
super.init(frame: .zero)
Expand Down Expand Up @@ -73,15 +73,15 @@ class SpacerView: UIView, _Spacer {

switch size {
case .width:
frame.size.width = length
frame.size.width = Swift.max(0, length)
case .height:
frame.size.height = length
frame.size.height = Swift.max(0, length)
}
self.setLength = length
self.setLength = Swift.max(0, length)
}

var minLength: CGFloat {
if setLength != -1 {
if setLength != 0 {
return setLength
}
if length != .greatestFiniteMagnitude {
Expand All @@ -107,7 +107,7 @@ class SpacerLayer: CALayer, _Spacer {
var min: CGFloat = .leastNonzeroMagnitude
var max: CGFloat = .greatestFiniteMagnitude

var setLength: CGFloat = -1
var setLength: CGFloat = 0

required init(length: CGFloat, min: CGFloat, max: CGFloat) {
super.init()
Expand Down Expand Up @@ -150,15 +150,15 @@ class SpacerLayer: CALayer, _Spacer {

switch size {
case .width:
frame.size.width = length
frame.size.width = Swift.max(0, length)
case .height:
frame.size.height = length
frame.size.height = Swift.max(0, length)
}
self.setLength = length
self.setLength = Swift.max(0, length)
}

var minLength: CGFloat {
if setLength != -1 {
if setLength != 0 {
return setLength
}
if length != .greatestFiniteMagnitude {
Expand Down
3 changes: 3 additions & 0 deletions Sources/StackKit/StackKitResultBuilders.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ extension _StackKitViewContentResultBuilderProvider {
public static func buildExpression(_ expression: Void) -> [UIView] {
[]
}
public static func buildExpression<T>(_ expression: StackKitCompatible<T>) -> [T] where T: UIView {
[expression.view]
}
}

// MARK: For VStack View
Expand Down
182 changes: 182 additions & 0 deletions Sources/StackKit/UIKit+FitSize.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
import UIKit

var _FitTypeKey = "_FitTypeKey"

public enum FitType {
case content

case width
case height

case widthFlexible
case heightFlexible

var isFlexible: Bool {
if case .widthFlexible = self {
return true
} else if case .heightFlexible = self {
return true
}
return false
}
}

protocol FitSize {
var stackKitFitType: FitType? { get set }
func _fitSize(with fitType: FitType?)
}

extension UIView: FitSize {

var stackKitFitType: FitType? {
get {
objc_getAssociatedObject(self, &_FitTypeKey) as? FitType
}
set {
objc_setAssociatedObject(self, &_FitTypeKey, newValue, .OBJC_ASSOCIATION_COPY_NONATOMIC)
}
}

func fitSize(with fitType: FitType) -> Self {
self.stackKitFitType = fitType
return self
}

func _fitSize(with fitType: FitType? = .content) {

defer {
setNeedsLayout()
}

var size = resolveSize()

if let w = size.width, let h = size.height { // 指定了 size(width & height) 优先使用 size
self.frame.size = CGSize(width: w, height: h)
return
}

guard let fitType = fitType else {
self.frame.size = sizeThatFits(CGSize(width: CGFloat.greatestFiniteMagnitude, height: .greatestFiniteMagnitude))
return
}

var fitWidth = CGFloat.greatestFiniteMagnitude
var fitHeight = CGFloat.greatestFiniteMagnitude

switch fitType {
case .width, .widthFlexible:
if let w = applyMinMax(toWidth: size.width) {
fitWidth = w
} else {
fitWidth = bounds.width
}
case .height, .heightFlexible:
if let h = applyMinMax(toHeight: size.height) {
fitHeight = h
} else {
fitHeight = bounds.height
}
case .content:
fitWidth = size.width ?? bounds.width
fitHeight = size.height ?? bounds.height
}

fitWidth = _validateValue(fitWidth)
fitHeight = _validateValue(fitHeight)

let sizeThatFits = sizeThatFits(CGSize(width: fitWidth, height: fitHeight))

switch fitType {
case .width, .height, .widthFlexible, .heightFlexible:
if fitWidth != .greatestFiniteMagnitude {
size.width = fitType.isFlexible ? sizeThatFits.width : fitWidth
} else {
size.width = sizeThatFits.width
}

if fitHeight != .greatestFiniteMagnitude {
size.height = fitType.isFlexible ? sizeThatFits.height : fitHeight
} else {
size.height = sizeThatFits.height
}
case .content:
size = Size(width: sizeThatFits.width, height: sizeThatFits.height)
}

size.width = applyMinMax(toWidth: size.width)
size.height = applyMinMax(toHeight: size.height)

self.frame.size = size.cgSize
}

private func _validateValue(_ value: CGFloat?) -> CGFloat {
guard let value = value, value > 0, value.isFinite else {
return .greatestFiniteMagnitude
}
return value
}

private func _fixedSize(_ size: inout Size) {
if let w = _width {
size.width = w
}
if let h = _height {
size.height = h
}
}
}

extension UIView {

struct Size {
var width: CGFloat?
var height: CGFloat?

var cgSize: CGSize {
CGSize(width: width ?? 0, height: height ?? 0)
}
}

func resolveSize() -> Size {
var size = Size()
if let _width = _width {
size.width = _width
}
if let _height = _height {
size.height = _height
}
return size
}

func applyMinMax(toWidth width: CGFloat?) -> CGFloat? {
var result = width

// Handle minWidth
if let minWidth = _minWidth, minWidth > (result ?? 0) {
result = minWidth
}

// Handle maxWidth
if let maxWidth = _maxWidth, maxWidth < (result ?? CGFloat.greatestFiniteMagnitude) {
result = maxWidth
}
return result
}

func applyMinMax(toHeight height: CGFloat?) -> CGFloat? {
var result = height

// Handle minWidth
if let minWidth = _minHeight, minWidth > (result ?? 0) {
result = minWidth
}

// Handle maxWidth
if let maxWidth = _maxHeight, maxWidth < (result ?? CGFloat.greatestFiniteMagnitude) {
result = maxWidth
}

return result
}

}
Loading