我創建了一個簡化的示例。我想如果有經驗的人運行這個,他們將很容易解釋這種行為......
在示例中,addView 按鈕在每次按下時都會創建一個新的子子視圖。每次生成一個子視圖時,這些子視圖應該是相同的,但只有在生成第三個子視圖時,我才能得到正確的結果。這是什么原因?
如果您在 drawRect 方法中取消注釋框架和邊界呼叫的重復,您在第二次嘗試時會得到正確的結果 - 我再次無法解釋這種行為。
import Cocoa
class ViewController: NSViewController {
let parentView = ParentView(frame: NSRect(x: 100, y: 10, width: 100, height: 400))
let yMax = 400
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(parentView)
}
@IBAction func addView(_ sender: Any) {
let childView = ChildView(frame: NSRect(x:0, y: 0, width: 20, height: 20))
self.parentView.addSubview(childView)
for v in parentView.subviews {
print("subview frame: ", v.frame)
}
}
}
class ParentView: NSView {
override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
guard let context = NSGraphicsContext.current?.cgContext else {
return
}
drawRect(ctx: context)
}
func drawRect(ctx: CGContext) {
let parentFrameMarker = NSRect(x: 0, y: 0, width: 100, height: 400)
ctx.addRect(parentFrameMarker)
ctx.setStrokeColor(CGColor(red: 1.0, green: 0.0, blue: 0.0, alpha: 1.0))
ctx.setLineWidth(1.0)
ctx.strokePath()
}
}
class ChildView: NSView {
let controller = ViewController()
override func draw(_ dirtyRect: NSRect) {
guard let context = NSGraphicsContext.current?.cgContext else {
return
}
let y = controller.yMax
resizeView(y: y)
drawRect(ctx: context, y: y)
}
func drawRect(ctx: CGContext, y: Int) {
let width = 20
let height = 40
// let currentY = y - height
//
// let origin = CGPoint(x: 0, y: currentY)
// let size = NSSize(width: width, height: height)
// self.setBoundsSize(size)
// self.setFrameSize(size)
// print("new frame: ", self.frame)
// print("new bounds: ", self.bounds)
// self.setFrameOrigin(origin)
let childFrameMarker = NSRect(x:0, y: 0, width: width, height: height)
print("childFrameMarker size: ", childFrameMarker.size)
ctx.addRect(childFrameMarker)
ctx.setStrokeColor(CGColor(red: 0.0, green: 1.0, blue: 0.0, alpha: 1.0))
ctx.setLineWidth(2.0)
ctx.strokePath()
}
func resizeView(y: Int) {
let width = 20
let height = 40
let currentY = y - height
let origin = CGPoint(x: 0, y: currentY)
let size = NSSize(width: width, height: height)
self.setBoundsSize(size)
self.setFrameSize(size)
self.setFrameOrigin(origin)
print("new frame: ", self.frame)
print("new bounds: ", self.bounds)
}
}
uj5u.com熱心網友回復:
AppKit 不希望您在其draw(_:)方法中更改視圖的框架。
當您單擊按鈕添加子視圖時,這就是一個事件。AppKit按以下順序處理每個事件:
呼叫事件的回呼。在這種情況下,回呼是您的
addView(_:)方法。呼叫
updateConstraints已needsUpdateConstraints設定的任何視圖。請注意,新創建的視圖會自動設定needsUpdateConstraints.如果視圖需要對自動布局約束進行大量更改,則這樣做
updateConstraints比在事件回呼中更有效。如果您只更改少量約束,請不要擔心。呼叫
layout已needsLayout設定的任何視圖。請注意,新創建的視圖會自動設定needsLayout.到 AppKit 呼叫
layout時,它已經更新了接收視圖的框架。接收視圖的layout方法負責更新視圖子視圖的框架,但不負責更新視圖本身。NSView實作layout基于自動布局約束更新子視圖的框架,因此如果您覆寫該方法,呼叫通常很super.layout()重要layout。呼叫
draw(_:)任何已needsDisplay設定或由于呼叫而具有任何無效區域的視圖setNeedsDisplay(_:)。請注意,新創建的視圖會自動將其整個邊界標記為無效。到 AppKit 呼叫
draw(_:)時,它已經更新了正在繪制的視圖的框架。
所有對 的呼叫updateConstraints都在對 的任何呼叫之前進行layout,所有對 的呼叫layout都在對 的任何呼叫之前進行draw(_:)。到 AppKit 呼叫draw(_:)時,它希望您的視圖具有正確的幀,并且不希望這些幀在另一個事件發生之前發生變化。
所以你的代碼中的問題是你沒有ChildView在 AppKit 期望你的時候更新每個框架。您應該在 中更新它layout,但您在 中更新它draw(_:)。
關于您的代碼的另一個奇怪的事情是,每個人都ChildView創建了自己的實體ViewController,只是為了訪問控制器的yMax屬性。我認為您可能想要創建該yMax屬性static,以便您可以在沒有實體的情況下訪問它,或者您希望每個ChildView都訪問現有的ViewController. 但既然ParentView布置每一個都是真正的作業ChildView,那就ParentView需要參考現有的ViewController.
這是我撰寫代碼的方式:
import Cocoa
class ViewController: NSViewController {
let parentView = ParentView(frame: NSRect(x: 100, y: 10, width: 100, height: 400))
let yMax = 400
override func viewDidLoad() {
super.viewDidLoad()
parentView.controller = self
self.view.addSubview(parentView)
}
@IBAction func addView(_ sender: Any) {
let childView = ChildView(frame: NSRect(x:0, y: 0, width: 20, height: 20))
parentView.addSubview(childView)
parentView.needsLayout = true
}
}
class ParentView: NSView {
weak var controller: ViewController?
override func layout() {
super.layout()
guard var y = controller?.yMax else { return }
for subview in subviews {
y -= 40
subview.frame = CGRect(x: 0, y: y, width: 20, height: 40)
}
}
override func draw(_ dirtyRect: NSRect) {
// No call to `super.draw(_:)` needed because ParentView is a direct subclass of NSView.
NSColor.red.set()
bounds.frame()
}
}
class ChildView: NSView {
override func draw(_ dirtyRect: NSRect) {
NSColor.green.set()
bounds.frame(withWidth: 2)
}
}
更新
你評論說:
我正在計算繪圖中的幾何圖形,如果需要,我會根據需要繪制的內容使用
setFrameOrigin并調整框架。setFrameSize我想有可能在ChildView創建實體之前計算“繪圖”的幾何形狀,然后用正確的框架進行實體化。需要畫的東西ChildView實際上不在ChildView課堂上。對這個先有雞還是先有蛋的問題有什么建議嗎?”
根據需要計算并存盤該資訊ChildView。
假設我們要ChildView繪制一個正多邊形。我們要指定多邊形的邊數和外接半徑,然后進行ChildView布局以完全適合多邊形。這是一種方法:
import Cocoa
class ViewController: NSViewController {
let parentView = ParentView(frame: NSRect(x: 100, y: 10, width: 100, height: 400))
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(parentView)
}
@IBAction func addView(_ sender: Any) {
let childView = ChildView(
sideCount: parentView.subviews.count 3,
radius: 30
)
parentView.addSubview(childView)
parentView.needsLayout = true
}
}
class ParentView: NSView {
override func layout() {
super.layout()
var y = bounds.size.height
for subview in subviews.lazy.compactMap({ $0 as? ChildView }) {
let size = subview.intrinsicContentSize
y -= size.height
subview.frame = CGRect(x: 0, y: y, width: size.width, height: size.height)
}
}
override func draw(_ dirtyRect: NSRect) {
// No call to `super.draw(_:)` needed because ParentView is a direct subclass of NSView.
NSColor.red.set()
bounds.frame()
}
}
class ChildView: NSView {
// Here is the raw data on which my appearance depends.
var sideCount: Int { didSet { invalidate() } }
var radius: CGFloat { didSet { invalidate() } }
// Here is cached layout information that depends on my raw data.
private var detailCache: Detail? = nil
private struct Detail {
var size: CGSize
var vertices: [CGPoint]
}
init(sideCount: Int, radius: CGFloat) {
self.sideCount = sideCount
self.radius = radius
super.init(frame: .zero)
}
required init?(coder: NSCoder) { fatalError() }
override var intrinsicContentSize: CGSize { detail.size }
override func draw(_ dirtyRect: NSRect) {
let vertices = detail.vertices
guard let first = vertices.first else { return }
let path = NSBezierPath()
path.move(to: first)
for p in vertices.dropFirst() {
path.line(to: p)
}
path.close()
NSColor.green.set()
path.fill()
}
private func invalidate() {
detailCache = nil
invalidateIntrinsicContentSize()
superview?.needsLayout = true
needsDisplay = true
}
private var detail: Detail {
if let detail = detailCache { return detail }
let detail = makeDetail()
detailCache = detail
return detail
}
private func makeDetail() -> Detail {
guard sideCount > 0 else { return Detail(size: .zero, vertices: []) }
var vertices: [CGPoint] = []
var pMin = CGPoint(x: CGFloat.infinity, y: .infinity)
var pMax = CGPoint(x: -CGFloat.infinity, y: -.infinity)
for i in (0 ..< sideCount).lazy.map({ CGFloat($0) }) {
let p = CGPoint(
x: radius * cos(2 * .pi * i / CGFloat(sideCount)),
y: radius * sin(2 * .pi * i / CGFloat(sideCount))
)
vertices.append(p)
pMin.x = min(pMin.x, p.x)
pMin.y = min(pMin.y, p.y)
pMax.x = max(pMax.x, p.x)
pMax.y = max(pMax.y, p.y)
}
for i in vertices.indices {
vertices[i].x -= pMin.x
vertices[i].y -= pMin.y
}
return Detail(
size: CGSize(
width: pMax.x - pMin.x,
height: pMax.y - pMin.y
),
vertices: vertices
)
}
}
uj5u.com熱心網友回復:
您的任何一個視圖都沒有布局約束。你不能用坐標定位你的視圖。
你應該看看自動布局。
當您使用自動布局時,一切都由視圖的內在內容大小以及內容擁抱和抗壓縮優先級控制。
因此,您需要覆寫視圖的內在內容大小,并為子視圖使用自動布局,并具有與父視圖相關的布局約束。您還應該洗掉 drawRect 中的所有代碼,這些代碼試圖控制視圖的位置或寬度/高度。
僅在 drawRect 方法中進行繪圖。
目前 AppKit 正在自動為您添加模棱兩可的自動布局約束。它沒有足夠的資訊來正確放置它們。這就是你所看到的。
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/428597.html
上一篇:在shuffle包中添加文本
