我构建了一个自定义视图作为UIView的子类。现在,我想在我的ViewController中设置此视图的约束,而不是设置框架。这是自定义视图类:
//
// PullUpView.swift
//
//
import UIKit
final internal class PullUpView: UIView, UIGestureRecognizerDelegate {
// MARK: - Variables
private var animator : UIDynamicAnimator!
private var collisionBehavior : UICollisionBehavior!
private var snapBehaviour : UISnapBehavior?
private var dynamicItemBehavior : UIDynamicItemBehavior!
private var gravityBehavior : UIGravityBehavior!
private var panGestureRecognizer : UIPanGestureRecognizer!
internal var delegate : PullUpViewDelegate?
// MARK: - Setup the view when bounds are defined.
override var bounds: CGRect {
didSet {
print(self.frame)
print(self.bounds)
setupStyle()
setupBehavior()
setupPanGesture()
}
}
override var frame: CGRect {
didSet {
print(self.frame)
print(self.bounds)
setupStyle()
setupBehavior()
setupPanGesture()
}
}
}
// MARK: - Behavior (Collision, Gravity, etc.)
private extension PullUpView {
final private func setupBehavior() {
guard let superview = superview else { return }
animator = UIDynamicAnimator(referenceView: superview)
dynamicItemBehavior = UIDynamicItemBehavior(items: [self])
dynamicItemBehavior.allowsRotation = false
dynamicItemBehavior.elasticity = 0
gravityBehavior = UIGravityBehavior(items: [self])
gravityBehavior.gravityDirection = CGVector(dx: 0, dy: 1)
collisionBehavior = UICollisionBehavior(items: [self])
configureContainer()
animator.addBehavior(gravityBehavior)
animator.addBehavior(dynamicItemBehavior)
animator.addBehavior(collisionBehavior)
}
final private func configureContainer() {
let boundaryWidth = UIScreen.main.bounds.size.width
let boundaryHeight = UIScreen.main.bounds.size.height
collisionBehavior.addBoundary(withIdentifier: "upper" as NSCopying, from: CGPoint(x: 0, y: 0), to: CGPoint(x: boundaryWidth, y: 0))
collisionBehavior.addBoundary(withIdentifier: "lower" as NSCopying, from: CGPoint(x: 0, y: boundaryHeight + self.frame.height - 66), to: CGPoint(x: boundaryWidth, y: boundaryHeight + self.frame.height - 66))
}
final private func snapToBottom() {
gravityBehavior.gravityDirection = CGVector(dx: 0, dy: 2.5)
}
final private func snapToTop() {
gravityBehavior.gravityDirection = CGVector(dx: 0, dy: -2.5)
}
}
// MARK: - Style (Corner-radius, background, etc.)
private extension PullUpView {
final private func setupStyle() {
self.backgroundColor = UIColor(red: 1, green: 1, blue: 1, alpha: 0)
let blurEffect = UIBlurEffect(style: .regular)
let blurEffectView = UIVisualEffectView(effect: blurEffect)
blurEffectView.frame = self.bounds
blurEffectView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
self.addSubview(blurEffectView)
self.layer.masksToBounds = true
self.clipsToBounds = true
self.isUserInteractionEnabled = true
let dragBar = UIView()
dragBar.backgroundColor = .gray
dragBar.frame = CGRect(x: (self.bounds.width / 2) - 5, y: self.bounds.origin.y + 5, width: 10, height: 2)
self.addSubview(dragBar)
}
}
// MARK: - Pan Gesture Recognizer and Handler Functions
internal extension PullUpView {
final private func setupPanGesture() {
panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(handlePanGesture(_:)))
panGestureRecognizer.cancelsTouchesInView = false
self.addGestureRecognizer(panGestureRecognizer)
}
@objc final private func handlePanGesture(_ panGestureRecognizer: UIPanGestureRecognizer) {
guard let superview = superview else { return }
if let viewCanMove: Bool = delegate?.viewCanMove?(pullUpView: self) {
if !viewCanMove {
return
}
}
let velocity = panGestureRecognizer.velocity(in: superview).y
var movement = self.frame
movement.origin.x = 0
movement.origin.y = movement.origin.y + (velocity * 0.05)
if panGestureRecognizer.state == .ended {
panGestureEnded()
} else if panGestureRecognizer.state == .began {
snapToBottom()
} else {
animator.removeBehavior(snapBehaviour ?? UIPushBehavior())
snapBehaviour = UISnapBehavior(item: self, snapTo: CGPoint(x: movement.midX, y: movement.midY))
animator.addBehavior(snapBehaviour ?? UIPushBehavior())
}
}
final private func panGestureEnded() {
animator.removeBehavior(snapBehaviour ?? UIPushBehavior())
let velocity = dynamicItemBehavior.linearVelocity(for: self)
if fabsf(Float(velocity.y)) > 250 {
if velocity.y < 0 {
moveViewIfPossible(direction: .up)
} else {
moveViewIfPossible(direction: .down)
}
} else {
if let superviewHeight = self.superview?.bounds.size.height {
// User scrolled over half of the screen...
if self.frame.origin.y > superviewHeight / 2 {
moveViewIfPossible(direction: .down)
} else {
moveViewIfPossible(direction: .up)
}
}
}
}
final private func moveViewIfPossible(direction: PullUpViewPanDirection) {
if let viewCanMove: Bool = delegate?.viewCanSnap?(pullUpView: self, direction: direction) {
if viewCanMove {
delegate?.viewWillMove?(pullUpView: self, direction: direction)
direction == .up ? snapToTop() : snapToBottom()
}
} else {
delegate?.viewWillMove?(pullUpView: self, direction: direction)
direction == .up ? snapToTop() : snapToBottom()
}
}
}
// MARK: - PullUpViewPanDirection declared inside
internal extension PullUpView {
/// An enum that defines whether the view moves up or down.
@objc
internal enum PullUpViewPanDirection: Int {
case
up,
down
}
}
但是当我设置约束时,我总是得到一个负起源的框架:
pullUpView.translatesAutoresizingMaskIntoConstraints = false
pullUpView.widthAnchor.constraint(equalTo: self.view.widthAnchor).isActive = true
pullUpView.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
pullUpView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor).isActive = true
pullUpView.topAnchor.constraint(equalTo: self.view.topAnchor).isActive = true
框架:
( -160.0 , -252.0 ,320.0,504.0)
界:
(0.0,0.0,320.0,504.0)
奇怪的是,如果我注释掉函数 setupBehavior(),视图会正确显示(但是一旦触发了平移手势处理程序,它就会失败。)
相反,如果我设置框架:
pullUpView.frame = CGRect(x: 0, y: self.view.bounds.height - 66, width: self.view.bounds.width, height: self.view.bounds.height)
它工作正常。
提前感谢您的帮助!
答案 0 :(得分:1)
查看您尝试设置的约束,看起来您需要一个视图控制器 - 让我们称之为MainViewController
- 拥有自定义视图,因为它是全屏视图。
使用自动布局时应该做的第一件事是忘记关于帧。在大多数情况下,忘记关于边界。接下来,我use the view life cycle并忘记在边界和帧上使用didSet
。我就是这样做的:
在viewDidLoad
中设置你可以做的一切 - 在你的情况下,它就是一切:
override func viewDidLoad() {
super.viewDidLoad()
pullUpView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(pullUpView)
pullUpView.widthAnchor.constraint(equalTo: self.view.widthAnchor).isActive = true
pullUpView.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
pullUpView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor).isActive = true
pullUpView.topAnchor.constraint(equalTo: self.view.topAnchor).isActive = true
}
就是这样!相关说明:
false
。做得好。如果你不这样做,你就会遇到约束冲突。更重要的是,如果你做,你基本上不需要担心边界或框架。constant: 10
或multiplier: 0.5
。< / LI>
setUpConstraints()
并将其设为extension
给视图控制器。最后一点,从Apple iTunesConnect收到的电子邮件中收集到的信息将在4月份发布,请使用所有设备的“安全区域”。这是我的所作所为:
let safeAreaView = UIView()
safeAreaView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(safeAreaView)
if #available(iOS 11, *) {
let guide = view.safeAreaLayoutGuide
safeAreaView.topAnchor.constraintEqualToSystemSpacingBelow(guide.topAnchor, multiplier: 1.0).isActive = true
safeAreaView.bottomAnchor.constraintEqualToSystemSpacingBelow(guide.bottomAnchor, multiplier: 1.0).isActive = true
} else {
safeAreaView.topAnchor.constraint(equalTo: topLayoutGuide.bottomAnchor).isActive = true
safeAreaView.bottomAnchor.constraint(equalTo: bottomLayoutGuide.topAnchor).isActive = true
}
safeAreaView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
safeAreaView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
还有其他方法可以执行此操作(这会创建UIView
)但现在您将所有约束设置为safeAreaView
而不是self.view
。
最后注意事项:如果必须引用一个帧,请在视图控制器视图生命周期中尽可能晚地进行,比如viewDidLayoutSubview
。虽然 bounds 可能早在viewDidLoad
就已知,但 frames 却不是。
我敢打赌,如果您追踪的是didSet
在您的平均视图加载或方向更改上发生了多少时间,您会发现它比您想象的要多。