以编程方式创建的 UIView 的框架为 0
Posted
技术标签:
【中文标题】以编程方式创建的 UIView 的框架为 0【英文标题】:Programmatically created UIView's frame is 0 【发布时间】:2018-12-06 16:35:48 【问题描述】:设置布局后无法访问 uiview 的框架。框架,中心为 0,0 点。
我还应该提到,这个项目中没有故事板。所有视图和一切都是以编程方式创建的。
我以编程方式创建了一个 UIView resultView
并将其添加为 scrollView
中的子视图,该子视图也添加为 view
的子视图,然后设置它的约束,锚定在一个名为 setupLayout()
我调用的方法中setupLayout()
在viewDidLoad()
中,在该方法之后,我调用另一个名为configureShapeLayer()
的方法。在configureShapeLayer()
内部,我尝试访问视图中心:
let center = resultView.center // should give resultView's center but gives 0
然后通过使用这个中心值,我尝试添加两个 bezierPaths 以获得状态栏类型的视图。但由于此时 resultView 的中心没有更新,因此它看起来错位了。请看下图:
我还尝试在loadView()
中调用setupLayout()
,然后在viewDidLoad()
中调用configureShapeLayer()
,但没有任何改变。
所以我需要一种方法来确保在调用configureShapeLayer()
之前在我的视图中设置所有视图、应用所有约束和布局。但是我该怎么做呢?
我也尝试在viewWillLayoutSubviews()
和viewDidLayoutSubviews()
两种方法中调用configureShapeLayer()
,但它使情况变得更糟,也没有工作。
整个视图控制器文件如下:首先声明视图,然后将它们添加到prepareUI()
中的视图中,在prepareUI()
的末尾,调用另一个方法setupLayout()
。完成布局设置后,从viewDidLoad可以看出,最终调用了configureShapeLayer()
方法。
import UIKit
class TryViewController: UIViewController
let score: CGFloat = 70
lazy var percentage: CGFloat =
return score / 100
()
// MARK: View Declarations
private let scrollView: UIScrollView =
let scrollView = UIScrollView()
scrollView.backgroundColor = .white
return scrollView
()
private let iconImageView: UIImageView =
let imageView = UIImageView()
imageView.contentMode = .scaleAspectFit
return imageView
()
let scoreLayer = CAShapeLayer()
let trackLayer = CAShapeLayer()
let percentageLabel: UILabel =
let label = UILabel()
label.text = ""
label.textAlignment = .center
label.font = TextStyle.systemFont(ofSize: 50.0)
return label
()
// This one is the one should have status bar at center.
private let resultView: UIView =
let view = UIView()
view.backgroundColor = .purple
return view
()
override func viewDidLoad()
super.viewDidLoad()
prepareUI()
configureShapeLayer()
private func prepareUI()
resultView.addSubviews(views: percentageLabel)
scrollView.addSubviews(views: iconImageView,
resultView)
view.addSubviews(views: scrollView)
setupLayout()
private func setupLayout()
scrollView.fillSuperview()
iconImageView.anchor(top: scrollView.topAnchor,
padding: .init(topPadding: 26.0))
iconImageView.widthAnchor.constraint(equalTo: scrollView.widthAnchor, multiplier: 0.31).isActive = true
iconImageView.heightAnchor.constraint(equalTo: iconImageView.widthAnchor, multiplier: 0.67).isActive = true
iconImageView.anchorCenterXToSuperview()
//percentageLabel.frame = CGRect(x: 0, y: 0, width: 105, height: 60)
//percentageLabel.center = resultView.center
percentageLabel.anchorCenterXToSuperview()
percentageLabel.anchorCenterYToSuperview()
let resultViewTopConstraintRatio: CGFloat = 0.104
resultView.anchor(top: iconImageView.bottomAnchor,
padding: .init(topPadding: (view.frame.height * resultViewTopConstraintRatio)))
resultView.widthAnchor.constraint(equalTo: scrollView.widthAnchor, multiplier: 0.533).isActive = true
resultView.heightAnchor.constraint(equalTo: resultView.widthAnchor, multiplier: 1.0).isActive = true
resultView.anchorCenterXToSuperview()
configureShapeLayer()
private func configureShapeLayer()
let endAngle = ((2 * percentage) * CGFloat.pi) - CGFloat.pi / 2
let center = resultView.center // should give resultView's center but gives 0
// Track Layer Part
let trackPath = UIBezierPath(arcCenter: center, radius: 50, startAngle: -CGFloat.pi / 2, endAngle: 2 * CGFloat.pi, clockwise: true)
trackLayer.path = trackPath.cgPath
trackLayer.strokeColor = UIColor.lightGray.cgColor // to make different
trackLayer.lineWidth = 10
trackLayer.fillColor = UIColor.clear.cgColor
trackLayer.lineCap = .round
resultView.layer.addSublayer(trackLayer)
// Score Fill Part
let scorePath = UIBezierPath(arcCenter: center, radius: 50, startAngle: -CGFloat.pi / 2, endAngle: endAngle, clockwise: true)
scoreLayer.path = scorePath.cgPath
scoreLayer.strokeColor = UIColor.red.cgColor
scoreLayer.lineWidth = 10
scoreLayer.fillColor = UIColor.clear.cgColor
scoreLayer.lineCap = .round
scoreLayer.strokeEnd = 0
resultView.layer.addSublayer(scoreLayer)
【问题讨论】:
您是说整个视图是通过编程方式创建的。您为什么不分享您的代码而不是您的假设? 您是对的,但出于提问的目的,也有多余的代码。我将尝试重构问题并通过编辑添加我的代码。 @MilanNosáľ 我现在已经在问题中添加了我的代码@MilanNosáľ 我只是快速浏览了代码 - 您似乎在percentageLabel
上使用了 autolyaout,同时您正尝试直接设置其框架。如果是这种情况,框架将始终由自动布局计算,因此任何直接框架设置都将无效
由于您需要在初始化中访问一些视图框架信息,您需要将该初始化(或其中的一部分)移动到 viewWillAppear ,所有视图都准备好显示。在 ViewdidLoad 中,视图仅加载到内存中,因此您无法获取帧属性。
【参考方案1】:
创建自定义视图会更好。这将允许您在视图大小更改时“自动”更新您的贝塞尔路径。
它还允许您将绘图代码远离控制器代码。
这是一个简单的例子。它在“resultView”上方添加了一个按钮 - 每次点击它都会将百分比增加 5(百分比从 5 开始进行演示):
//
// PCTViewController.swift
//
// Created by Don Mag on 12/6/18.
//
import UIKit
class MyResultView: UIView
let scoreLayer = CAShapeLayer()
let trackLayer = CAShapeLayer()
var percentage = CGFloat(0.0)
didSet
self.percentageLabel.text = "\(Int(percentage * 100))%"
self.setNeedsLayout()
let percentageLabel: UILabel =
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.text = ""
label.textAlignment = .center
label.font = UIFont.systemFont(ofSize: 40.0)
return label
()
override init(frame: CGRect)
super.init(frame: frame)
commonInit()
required init?(coder aDecoder: NSCoder)
super.init(coder: aDecoder)
commonInit()
func commonInit() -> Void
layer.addSublayer(trackLayer)
layer.addSublayer(scoreLayer)
addSubview(percentageLabel)
NSLayoutConstraint.activate([
percentageLabel.centerXAnchor.constraint(equalTo: centerXAnchor),
percentageLabel.centerYAnchor.constraint(equalTo: centerYAnchor)
])
override func layoutSubviews()
super.layoutSubviews()
let endAngle = ((2 * percentage) * CGFloat.pi) - CGFloat.pi / 2
trackLayer.frame = self.bounds
scoreLayer.frame = self.bounds
let centerPoint = CGPoint(x: self.bounds.width / 2.0, y: self.bounds.height / 2.0)
// Track Layer Part
let trackPath = UIBezierPath(arcCenter: centerPoint, radius: 50, startAngle: -CGFloat.pi / 2, endAngle: 2 * CGFloat.pi, clockwise: true)
trackLayer.path = trackPath.cgPath
trackLayer.strokeColor = UIColor.lightGray.cgColor // to make different
trackLayer.lineWidth = 10
trackLayer.fillColor = UIColor.clear.cgColor
// pre-Swift 4.2
trackLayer.lineCap = kCALineCapRound
// trackLayer.lineCap = .round
// Score Fill Part
let scorePath = UIBezierPath(arcCenter: centerPoint, radius: 50, startAngle: -CGFloat.pi / 2, endAngle: endAngle, clockwise: true)
scoreLayer.path = scorePath.cgPath
scoreLayer.strokeColor = UIColor.red.cgColor
scoreLayer.lineWidth = 10
scoreLayer.fillColor = UIColor.clear.cgColor
// pre-Swift 4.2
scoreLayer.lineCap = kCALineCapRound
// scoreLayer.lineCap = .round
class PCTViewController: UIViewController
let resultView: MyResultView =
let v = MyResultView()
v.translatesAutoresizingMaskIntoConstraints = false
v.backgroundColor = .purple
return v
()
let btn: UIButton =
let b = UIButton()
b.translatesAutoresizingMaskIntoConstraints = false
b.setTitle("Add 5 percent", for: .normal)
b.backgroundColor = .blue
return b
()
var pct = 5
@objc func incrementPercent(_ sender: Any)
pct += 5
resultView.percentage = CGFloat(pct) / 100.0
override func viewDidLoad()
super.viewDidLoad()
view.addSubview(btn)
view.addSubview(resultView)
NSLayoutConstraint.activate([
btn.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20.0),
btn.centerXAnchor.constraint(equalTo: view.centerXAnchor),
resultView.widthAnchor.constraint(equalToConstant: 300.0),
resultView.heightAnchor.constraint(equalTo: resultView.widthAnchor),
resultView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
resultView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
])
btn.addTarget(self, action: #selector(incrementPercent), for: .touchUpInside)
resultView.percentage = CGFloat(pct) / 100.0
结果:
【讨论】:
看起来不错,我明天会尝试实施并通知您,非常感谢! 另一个优势是,只需对代码进行几次编辑,您就可以将其设为@IBDesignable
对象...因此您可以将其放置在 Storyboard 中的视图中,并查看它在设计过程中的外观-时间。
是的,以后在带有故事板的项目中使用它也是完美的。再次非常感谢你:)【参考方案2】:
这不是理想的解决方法,但请尝试在主线程上调用 configureShapeLayer()
func,如下所示:
DispatchQueue.main.async
configureShapeLayer()
我曾经遇到过这样的问题,并且是这样的。
【讨论】:
这只是获得更好解决方案的提示以上是关于以编程方式创建的 UIView 的框架为 0的主要内容,如果未能解决你的问题,请参考以下文章