- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
我试图将 View 元素放置在 UIImageView 中,然后对 UIImageView 的宽度进行动画处理以填充 super View 。我正在使用自动布局约束来定位 View 并为其设置动画。在动画开始时,圆正好位于 superView 的 centerX 的一半处:
self.circleXConstraint = NSLayoutConstraint(item: self.circle, attribute: .centerX, relatedBy: .equal, toItem: self.bodyView, attribute: .centerX, multiplier: 0.5, Constant: 0)
但是当动画开始时,圆圈会偏离正确的位置(两条线交叉的位置)。
我花了几天时间试图找出为什么自动布局不能保持圆形 View 正确定位,但无法找出原因。
Xcode Playground 的完整代码可以在这里下载: https://github.com/imyrvold/Animate-body
Xcode Playground swift 代码:
import UIKit
import PlaygroundSupport
class ViewController : UIViewController {
lazy var bodyView: UIImageView = {
let bodyView = UIImageView()
bodyView.contentMode = .scaleAspectFill
bodyView.image = self.image
bodyView.backgroundColor = .yellow
return bodyView
}()
lazy var image: UIImage? = {
UIImage(named: "body")
}()
lazy var button: UIButton = {
let button = UIButton(frame: CGRect(x: 0, y: 0, width: 100, height: 50))
button.setTitle("Start", for: .normal)
button.addTarget(self, action: #selector(headZoom), for: .touchDown)
button.titleLabel?.textColor = .black
button.backgroundColor = .lightGray
return button
}()
lazy var circle: BodyAreaView = {
let circle = BodyAreaView(frame: CGRect(x: 0, y: 0, width: 48, height: 48))
circle.backgroundColor = .clear
return circle
}()
// bodyView constraints
var topAnchorConstraint: NSLayoutConstraint!
var centerXAnchorConstraint: NSLayoutConstraint!
var aspectConstraint: NSLayoutConstraint!
var superWidthConstraint: NSLayoutConstraint!
var iphoneHeightConstraint: NSLayoutConstraint!
var iphoneWidthConstraint: NSLayoutConstraint!
// circle constraints
var circleHeightConstraint: NSLayoutConstraint!
var circleXConstraint: NSLayoutConstraint!
var circleYConstraint: NSLayoutConstraint!
var circleAspectConstraint: NSLayoutConstraint!
override func loadView() {
let view = UIView()
view.backgroundColor = .white
self.view = view
view.addSubview(self.bodyView)
self.bodyView.addSubview(self.circle)
view.addSubview(self.button)
self.view.translatesAutoresizingMaskIntoConstraints = false
}
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
self.setBodyViewConstraints()
self.setFibroConstraints()
self.bodyView.translatesAutoresizingMaskIntoConstraints = false
self.circle.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
self.topAnchorConstraint,
self.centerXAnchorConstraint,
self.aspectConstraint,
self.iphoneWidthConstraint
])
NSLayoutConstraint.activate([
self.circleHeightConstraint,
self.circleXConstraint,
self.circleYConstraint,
self.circleAspectConstraint
])
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
}
@objc func headZoom() {
self.superWidthConstraint.isActive = true
print("body view before: \(self.bodyView.bounds)")
print("circle origin before: \(self.circle.frame.origin)")
UIView.animate(withDuration: 3, animations: {
self.view.layoutIfNeeded()
}) { _ in
print("body view after: \(self.bodyView.bounds)")
print("circle frame after: \(self.circle.frame)")
}
}
func setBodyViewConstraints() {
self.topAnchorConstraint = self.bodyView.topAnchor.constraint(equalTo: self.view.topAnchor)
self.centerXAnchorConstraint = self.bodyView.centerXAnchor.constraint(equalTo: self.view.centerXAnchor)
self.aspectConstraint = self.bodyView.heightAnchor.constraint(equalTo: self.bodyView.widthAnchor, multiplier: 1388/408.0)
self.superWidthConstraint = self.bodyView.widthAnchor.constraint(equalTo: self.view.widthAnchor, multiplier: 1)
self.iphoneHeightConstraint = self.bodyView.heightAnchor.constraint(equalToConstant: 633)
self.iphoneWidthConstraint = self.bodyView.widthAnchor.constraint(equalToConstant: 186)
}
func setFibroConstraints() {
self.circleHeightConstraint = self.circle.heightAnchor.constraint(equalTo: self.bodyView.heightAnchor, multiplier: 0.05)
self.circleXConstraint = NSLayoutConstraint(item: self.circle, attribute: .centerX, relatedBy: .equal, toItem: self.bodyView, attribute: .centerX, multiplier: 0.5, constant: 0)
self.circleYConstraint = NSLayoutConstraint(item: self.circle, attribute: .centerY, relatedBy: .equal, toItem: self.bodyView, attribute: .centerY, multiplier: 0.5, constant: 0)
self.circleAspectConstraint = self.circle.heightAnchor.constraint(equalTo: self.circle.widthAnchor, multiplier: 1)
}
}
let viewController = ViewController()
viewController.preferredContentSize = CGSize(width: 375, height: 812)
PlaygroundPage.current.liveView = viewController
我已经更新了 Xcode Playground,现在它将按照建议以 .cyan 颜色显示圆形 View 的背景。
听起来,更新 UIView 子类的绘图代码是一项简单的任务,但在堆栈溢出上搜索解决方案几个小时后,没有一篇文章有任何帮助。看起来圆圈拒绝重新绘制自身,即使 View 本身的边界发生变化。
我简化了 UIView 子类代码,以便更容易理解绘图的工作原理。我很乐意为此获得任何帮助。我看过的许多帖子都建议重写layoutSubviews,将子层框架设置为 View 层边界,但这在这种情况下不起作用。一定是我忽略了一些东西。
这是 BodyAreaView 的代码,简化为仅绘制圆环:
import UIKit
public class BodyAreaView: UIView {
var ringColor: UIColor = .black
lazy var ringLayer: CAShapeLayer = {
let ringLayer = CAShapeLayer()
let ringPath = UIBezierPath(ovalIn: self.bounds.insetBy(dx: 4, dy: 4))
ringLayer.fillColor = UIColor.clear.cgColor
ringLayer.strokeColor = UIColor.black.cgColor
ringLayer.path = ringPath.cgPath
ringLayer.name = "ring"
ringLayer.needsDisplayOnBoundsChange = true
return ringLayer
}()
override public func draw(_ rect: CGRect) {
self.layer.addSublayer(self.ringLayer)
}
override public func layoutSubviews() {
guard let sublayers = self.layer.sublayers else { return }
for layer in sublayers {
layer.frame = layer.bounds
}
}
}
最佳答案
首先,您没有考虑到布局可能会出现多次。因此,在您的override func viewDidLayoutSubviews()
中,您会一遍又一遍地重新激活相同的约束。这意味着当你说
self.superWidthConstraint.isActive = true
您遇到了约束冲突,因为您的iphoneWidthConstraint
仍然处于事件状态:您要求两者旧的宽度约束和新的宽度约束。事实上,您看到任何宽度变化纯粹是运气。
解决办法如下。首先,编写带有标志的 layout
代码,以便仅初始化所有内容一次:
var didInitialLayout = false
override func viewDidLayoutSubviews() {
if didInitialLayout { return } // *
didInitialLayout = true //
// ...
其次,在激活新的宽度限制之前停用旧的宽度限制:
@objc func headZoom() {
self.iphoneWidthConstraint.isActive = false // *
self.superWidthConstraint.isActive = true
// ...
好的,现在冲突已经消失了。
现在我们可以专注于绘图代码了!该代码(您的 BodyAreaView)有很多问题,我不知道从哪里开始。但是,总结一下:
override public func draw(_ rect: CGRect) {
self.layer.addSublayer(self.ringLayer)
}
这里我们有一个典型的错误:绘制
,而您不绘制。相反,您会做一些完全无关的事情:添加一个层。这总是很危险的,因为draw
可以被调用很多次;在最坏的情况下,您最终可能会一遍又一遍地添加此层。 (在这里,这可能不会发生,但这只是运气。)
下一个:
override public func layoutSubviews() {
您似乎认为在动画播放期间您会多次收到该消息。你不。而且你忘记调用 super
这可能是灾难性的。
最后,您对形状图层的使用:
lazy var ringLayer: CAShapeLayer = {
let ringLayer = CAShapeLayer()
let ringPath = UIBezierPath(ovalIn: self.bounds.insetBy(dx: 4, dy: 4))
ringLayer.fillColor = UIColor.clear.cgColor
ringLayer.strokeColor = UIColor.black.cgColor
ringLayer.path = ringPath.cgPath
ringLayer.name = "ring"
ringLayer.needsDisplayOnBoundsChange = true
return ringLayer
}()
首先,您的图层没有框架,因此没有大小。其次,此代码将仅运行一次。因此,您将获得一条路径,一次,然后就不会发生任何会改变路径大小的事情。当 View 发生变化时,ringPath 半径不会神奇地永远与 View 的大小相关联。它是按照一定的尺寸创建的,仅此而已。这就是为什么您实际上看不到路径发生变化的原因。 (使用needsDisplayOnBoundsChange
是完全无关的,因为你没有一个可以显示的图层!那将是你进行绘图的图层,而这并不是什么你有。)
好吧,最简单的解决方案是什么?我首先要做的就是完全丢弃图层并只绘制圆圈:
public class BodyAreaView: UIView {
override public func draw(_ rect: CGRect) {
let ringPath = UIBezierPath(ovalIn: self.bounds.insetBy(dx: 4, dy: 4))
let con = UIGraphicsGetCurrentContext()!
con.addPath(ringPath.cgPath)
con.strokePath()
}
}
效果非常好,因为我们绘制了一次,从那时起我们就使用缓存的圆形绘图,它会随着 View 的增长而增长。如果我们再次绘制,我们将根据当时 View 的大小按比例绘制,因此圆圈看起来总是正确的。
好的,但这并不能真正回答您最初的问题,即如何使用图层来做到这一点。好吧,答案又是:不要使用形状图层,因为那不是自动重绘图层。使用自定义图层执行此操作,该图层知道如何根据当前边界大小将自身重新绘制为圆形。例如:
public class BodyAreaView: UIView {
class Circle : CALayer {
override func draw(in con: CGContext) {
let ringPath = UIBezierPath(ovalIn: self.bounds.insetBy(dx: 4, dy: 4))
con.addPath(ringPath.cgPath)
con.strokePath()
}
}
let circle = Circle()
public override init(frame: CGRect) {
super.init(frame:frame)
self.circle.needsDisplayOnBoundsChange = true
self.circle.frame = self.bounds
self.layer.addSublayer(self.circle)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
public override func layoutSubviews() {
super.layoutSubviews()
if let layers = self.layer.sublayers {
for lay in layers {
lay.frame = self.bounds
}
}
}
}
但是,请注意,这会导致圆圈的大小在动画开始时跳跃。它跳到正确的位置,即动画结束时圆圈需要出现的位置。那是因为你没有获得图层的自动布局动画。这就是为什么我更喜欢第一个解决方案;至少在那里,圆圈随着视野而变大。
但是您可以通过单独设置图层大小的动画来解决该问题。这留给读者作为练习。 :)
关于swift - 在 Xcode Playground 中制作动画时,位置元素与 super View 成比例,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51694042/
最初我读到你不能在操场上尝试触摸或手势。然后我发现 touchesMoved 或点击手势会使用react。但是,我现在正在测试 UIPinchGestureRecognizer。没有可以让您在操场上使
我最近尝试打开/编辑 .playgroundbook project在 Xcode 版本 8.3.2 (8E2002) 中,有点惊讶它没有作为“liveView Playground”打开(我意识到它
我想在 Xcode 6.0.1 中使用 playground。所以文件 -> 新建 -> Playground 我立即得到错误。 Playground 执行终止,因为 Playground 进程意外退
在 Xcode 7 playgrounds 中运行最简单的默认代码...得到以下错误 Playground execution failed: Execution was interrupted, r
我已经开始了 Swift 编程入门类(class),到目前为止进展顺利,但我无法使用 Mac 来运行 Xcode。我确实有 iPad 版 Swift Playgrounds,它允许我完成作业并测试它们
GuidedTour Playground包含交互式和非交互式元素以及格式良好的文本。文本似乎已被锁定,无法编辑。我发现它是一个很好的学习工具。是否可以编辑其内容或创建类似的内容? 最佳答案 Play
我在 Xcode Playground 和 Swift Playground 上运行这段代码: import SwiftUI import PlaygroundSupport struct Conte
我正在 Xcode Playground 上玩弄 REST API,我需要用 SHA1 散列一些东西。我找到的所有解决方案都依赖于 Common Crypto,而这似乎不能直接在 Swift play
我知道如何在常规应用程序项目中使用 Xcode 11 中的 SwiftUI,但我想知道即使我不能使用实时编辑器,是否也可以在 playgrounds 中使用它? 最佳答案 当然,这很简单: impor
我知道如何在常规应用程序项目中使用 Xcode 11 中的 SwiftUI,但我想知道即使我不能使用实时编辑器,是否也可以在 playgrounds 中使用它? 最佳答案 当然,这很简单: impor
新创建的 Xcode 10 playground 提供了一个播放按钮来逐步执行 playground。 如果我打开在 iPad 或以前版本的 Xcode 中创建的 Playground ,播放按钮会丢
我注意到 Xcode 10 中的 Playgrounds 不再允许使用已声明但未初始化的变量。例如: 虽然此代码可以在 Xcode 9 playground 中运行,但在 Xcode 10 playg
我已经更新到 Xcode 6,最新的出现在 AppStore 上。我正在运行 OS X Yosemite Beta 3,显然,我只能打开 iOS playground,列表中缺少 OS X。 我附上了
我在 xcode 中开发了一个 ios 应用程序,我想在 swift playground 中运行这个应用程序。所以我想将我的 .xcodeproj 项目转换为 .playground 格式。那么有什
我尝试使用以下代码行创建滤色器 let parameters = [kCIInputColorKey: CIColor(string: "0.5 0.7 0.3 1.0")] let colorFil
我尝试使用以下代码行创建滤色器 let parameters = [kCIInputColorKey: CIColor(string: "0.5 0.7 0.3 1.0")] let colorFil
每次我创建一个新的 playground 以测试一些代码时,Xcode 都会卡住并且不会运行代码。它只是在屏幕顶部显示“Running playground”或“Launching simulator
我在应用程序开发方面相对较新,但出于兴趣,我想在 Xcode 上的 Playground 项目上实现此应用程序: http://merowing.info/2015/11/the-beauty-of-
我正在尝试使用 GraphQL 记录我的 API。对于可读性问题,我想在多行中留下评论,但它似乎不适用于常规的 '\n' 换行符 """ Return:\n true : DB save succ
我是 Swift 世界的新手。 如何将带逗号的字符串转换为带小数的字符串? 代码使用点 (.) 就可以正常工作 问题是当我使用逗号 (,) ... 时:var Price 问题的根源是十进制法语键盘使
我是一名优秀的程序员,十分优秀!