ReadViewSDK/ReadViewDemo/Pods/SnapKit/Sources/Constraint.swift
shenlei 1422461224 将 PDF 成品阅读控制器下沉为 RDPDFReaderView SDK,并修复手机横屏单页连续竖滑
SDK 化收尾:
- 新增 RDPDFReaderView 本地 pod(阅读容器、成品控制器、划线/注释、画笔、
  目录/设置面板、主题与持久化契约),Demo 精简为 PDFKit 页面提供者 + 持久化宿主
- Podfile/工程接入 RDPDFReaderView 与 SnapKit,示例书改用 PDF 阅读器示例文件

手机横屏单页竖滑修复:
- cell 高度改为宽度适配后的纸张高度(sizeForItemAt + 宿主按页提供纵横比),
  纵向超出一屏的内容由外层 collectionView 连续滚动承接,不再被裁掉
- 页面内部不再开启同向嵌套的兜底竖向拖动,修复外层滚动被吞、无法滚到下一页
- 修复 applyContentSize 先评估拖动开关后写 contentSize 的顺序缺陷
- 页码按视口中心命中 cell 计算,补 didEndDragging 兜底;旋转重锚点与
  transitionToPage 改用真实布局位置
- 横竖屏同为竖滑时也重建 cell 并刷新全部可见页的适配配置
- 横屏单页点击不再跳页,只显隐工具栏;翻页交给连续竖滑

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 18:49:44 +09:00

342 lines
13 KiB
Swift

//
// SnapKit
//
// Copyright (c) 2011-Present SnapKit Team - https://github.com/SnapKit
//
// 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.
#if canImport(UIKit)
import UIKit
#else
import AppKit
#endif
public final class Constraint {
internal let sourceLocation: (String, UInt)
internal let label: String?
private let from: ConstraintItem
private let to: ConstraintItem
private let relation: ConstraintRelation
private let multiplier: ConstraintMultiplierTarget
private var constant: ConstraintConstantTarget {
didSet {
self.updateConstantAndPriorityIfNeeded()
}
}
private var priority: ConstraintPriorityTarget {
didSet {
self.updateConstantAndPriorityIfNeeded()
}
}
public var layoutConstraints: [LayoutConstraint]
public var isActive: Bool {
set {
if newValue {
activate()
}
else {
deactivate()
}
}
get {
for layoutConstraint in self.layoutConstraints {
if layoutConstraint.isActive {
return true
}
}
return false
}
}
// MARK: Initialization
internal init(from: ConstraintItem,
to: ConstraintItem,
relation: ConstraintRelation,
sourceLocation: (String, UInt),
label: String?,
multiplier: ConstraintMultiplierTarget,
constant: ConstraintConstantTarget,
priority: ConstraintPriorityTarget) {
self.from = from
self.to = to
self.relation = relation
self.sourceLocation = sourceLocation
self.label = label
self.multiplier = multiplier
self.constant = constant
self.priority = priority
self.layoutConstraints = []
// get attributes
let layoutFromAttributes = self.from.attributes.layoutAttributes
let layoutToAttributes = self.to.attributes.layoutAttributes
// get layout from
let layoutFrom = self.from.layoutConstraintItem!
// get relation
let layoutRelation = self.relation.layoutRelation
for layoutFromAttribute in layoutFromAttributes {
// get layout to attribute
let layoutToAttribute: LayoutAttribute
#if canImport(UIKit)
if layoutToAttributes.count > 0 {
if self.from.attributes == .edges && self.to.attributes == .margins {
switch layoutFromAttribute {
case .left:
layoutToAttribute = .leftMargin
case .right:
layoutToAttribute = .rightMargin
case .top:
layoutToAttribute = .topMargin
case .bottom:
layoutToAttribute = .bottomMargin
default:
fatalError()
}
} else if self.from.attributes == .margins && self.to.attributes == .edges {
switch layoutFromAttribute {
case .leftMargin:
layoutToAttribute = .left
case .rightMargin:
layoutToAttribute = .right
case .topMargin:
layoutToAttribute = .top
case .bottomMargin:
layoutToAttribute = .bottom
default:
fatalError()
}
} else if self.from.attributes == .directionalEdges && self.to.attributes == .directionalMargins {
switch layoutFromAttribute {
case .leading:
layoutToAttribute = .leadingMargin
case .trailing:
layoutToAttribute = .trailingMargin
case .top:
layoutToAttribute = .topMargin
case .bottom:
layoutToAttribute = .bottomMargin
default:
fatalError()
}
} else if self.from.attributes == .directionalMargins && self.to.attributes == .directionalEdges {
switch layoutFromAttribute {
case .leadingMargin:
layoutToAttribute = .leading
case .trailingMargin:
layoutToAttribute = .trailing
case .topMargin:
layoutToAttribute = .top
case .bottomMargin:
layoutToAttribute = .bottom
default:
fatalError()
}
} else if self.from.attributes == self.to.attributes {
layoutToAttribute = layoutFromAttribute
} else {
layoutToAttribute = layoutToAttributes[0]
}
} else {
if self.to.target == nil && (layoutFromAttribute == .centerX || layoutFromAttribute == .centerY) {
layoutToAttribute = layoutFromAttribute == .centerX ? .left : .top
} else {
layoutToAttribute = layoutFromAttribute
}
}
#else
if self.from.attributes == self.to.attributes {
layoutToAttribute = layoutFromAttribute
} else if layoutToAttributes.count > 0 {
layoutToAttribute = layoutToAttributes[0]
} else {
layoutToAttribute = layoutFromAttribute
}
#endif
// get layout constant
let layoutConstant: CGFloat = self.constant.constraintConstantTargetValueFor(layoutAttribute: layoutToAttribute)
// get layout to
var layoutTo: AnyObject? = self.to.target
// use superview if possible
if layoutTo == nil && layoutToAttribute != .width && layoutToAttribute != .height {
layoutTo = layoutFrom.superview
}
// create layout constraint
let layoutConstraint = LayoutConstraint(
item: layoutFrom,
attribute: layoutFromAttribute,
relatedBy: layoutRelation,
toItem: layoutTo,
attribute: layoutToAttribute,
multiplier: self.multiplier.constraintMultiplierTargetValue,
constant: layoutConstant
)
// set label
layoutConstraint.label = self.label
// set priority
layoutConstraint.priority = LayoutPriority(rawValue: self.priority.constraintPriorityTargetValue)
// set constraint
layoutConstraint.constraint = self
// append
self.layoutConstraints.append(layoutConstraint)
}
}
// MARK: Public
@available(*, deprecated, renamed:"activate()")
public func install() {
self.activate()
}
@available(*, deprecated, renamed:"deactivate()")
public func uninstall() {
self.deactivate()
}
public func activate() {
self.activateIfNeeded()
}
public func deactivate() {
self.deactivateIfNeeded()
}
@discardableResult
public func update(offset: ConstraintOffsetTarget) -> Constraint {
self.constant = offset.constraintOffsetTargetValue
return self
}
@discardableResult
public func update(inset: ConstraintInsetTarget) -> Constraint {
self.constant = inset.constraintInsetTargetValue
return self
}
#if canImport(UIKit)
@discardableResult
@available(iOS 11.0, tvOS 11.0, *)
public func update(inset: ConstraintDirectionalInsetTarget) -> Constraint {
self.constant = inset.constraintDirectionalInsetTargetValue
return self
}
#endif
@discardableResult
public func update(priority: ConstraintPriorityTarget) -> Constraint {
self.priority = priority.constraintPriorityTargetValue
return self
}
@discardableResult
public func update(priority: ConstraintPriority) -> Constraint {
self.priority = priority.value
return self
}
@available(*, deprecated, renamed:"update(offset:)")
public func updateOffset(amount: ConstraintOffsetTarget) -> Void { self.update(offset: amount) }
@available(*, deprecated, renamed:"update(inset:)")
public func updateInsets(amount: ConstraintInsetTarget) -> Void { self.update(inset: amount) }
@available(*, deprecated, renamed:"update(priority:)")
public func updatePriority(amount: ConstraintPriorityTarget) -> Void { self.update(priority: amount) }
@available(*, deprecated, message:"Use update(priority: ConstraintPriorityTarget) instead.")
public func updatePriorityRequired() -> Void {}
@available(*, deprecated, message:"Use update(priority: ConstraintPriorityTarget) instead.")
public func updatePriorityHigh() -> Void { fatalError("Must be implemented by Concrete subclass.") }
@available(*, deprecated, message:"Use update(priority: ConstraintPriorityTarget) instead.")
public func updatePriorityMedium() -> Void { fatalError("Must be implemented by Concrete subclass.") }
@available(*, deprecated, message:"Use update(priority: ConstraintPriorityTarget) instead.")
public func updatePriorityLow() -> Void { fatalError("Must be implemented by Concrete subclass.") }
// MARK: Internal
internal func updateConstantAndPriorityIfNeeded() {
for layoutConstraint in self.layoutConstraints {
let attribute = (layoutConstraint.secondAttribute == .notAnAttribute) ? layoutConstraint.firstAttribute : layoutConstraint.secondAttribute
layoutConstraint.constant = self.constant.constraintConstantTargetValueFor(layoutAttribute: attribute)
let requiredPriority = ConstraintPriority.required.value
if (layoutConstraint.priority.rawValue < requiredPriority), (self.priority.constraintPriorityTargetValue != requiredPriority) {
layoutConstraint.priority = LayoutPriority(rawValue: self.priority.constraintPriorityTargetValue)
}
}
}
internal func activateIfNeeded(updatingExisting: Bool = false) {
guard let item = self.from.layoutConstraintItem else {
print("WARNING: SnapKit failed to get from item from constraint. Activate will be a no-op.")
return
}
let layoutConstraints = self.layoutConstraints
if updatingExisting {
var existingLayoutConstraints: [LayoutConstraint] = []
for constraint in item.constraints {
existingLayoutConstraints += constraint.layoutConstraints
}
for layoutConstraint in layoutConstraints {
let existingLayoutConstraint = existingLayoutConstraints.first { $0 == layoutConstraint }
guard let updateLayoutConstraint = existingLayoutConstraint else {
fatalError("Updated constraint could not find existing matching constraint to update: \(layoutConstraint)")
}
let updateLayoutAttribute = (updateLayoutConstraint.secondAttribute == .notAnAttribute) ? updateLayoutConstraint.firstAttribute : updateLayoutConstraint.secondAttribute
updateLayoutConstraint.constant = self.constant.constraintConstantTargetValueFor(layoutAttribute: updateLayoutAttribute)
}
} else {
NSLayoutConstraint.activate(layoutConstraints)
item.add(constraints: [self])
}
}
internal func deactivateIfNeeded() {
guard let item = self.from.layoutConstraintItem else {
print("WARNING: SnapKit failed to get from item from constraint. Deactivate will be a no-op.")
return
}
let layoutConstraints = self.layoutConstraints
NSLayoutConstraint.deactivate(layoutConstraints)
item.remove(constraints: [self])
}
}