I cannot figure out how to accomplish what seems to be a very simple task. I have a macOS cocoa application which consists of one window with a custom view inside. The view is 600x500 points. Inside this view is a subwiew that spans the whole width of the main view, but only a small fraction of the view's heigth. Its vertical origin is at about y=200.
The subview draws a rect starting 20 points off the left and right border, about 30 points in heigth. It also uses its layer with a CAEmitter layer as sublayer. This emitter's position can be controlled by the user, so its position will be updated by a function called from outside the view.
What I want:
When the user resizes the window, it should only scale proportionally (I managed to to that), so that the window's content as a whole gets resized. No subviews should get rearranged in any way, they all should keep their size and positions relative to each other, just getting bigger/smaller.
I do not get this to work.
What I have tried so far:
- With no additional properties set I can resize the window, but the subiew does not scale at all, it sticks to its position in the superview.
- I set
subView.leadingAnchor.constraint(greaterThanOrEqualTo: view.leadingAnchor).isActive = true
and the same for the trailingAnchor, additionally I setsubView.autoresizingMask = [.width]
Now the width of the rect drawn in the subview gets resized, but the rect's edges stay away 20 points from the border. I want this distance to be scaled as well. - The rect's heigth stays fixed. When I add
-heigth
to the subview's autoresizing mask the heigth gets distorted in a riculously disproportianal way (the distance to top and bottom margins are kept constant, so although the subview's heigth is only about 30 at start the rect gets blown up on resizing. - The emitter does not get repositioned. Its horizontal position gets calculated on user interaction and is relative to the view's borders, after resizing it sticks to its absloute position. I solved that by calling the calculating function from the draw() method, but that does not seem right to me
There are so many entry points in the cocoa framework regarding sizes, anchors, constraints etc., I simply do not know where to start, so any help would be appreciated.
UPDATE: I added code stripped down to the min:
class MyViewController: NSViewController {
public init () {
super.init(nibName: nil, bundle: nil)
self.view = MyView(frame: NSRect(x: 0, y: 0, width: 600, height: 500))
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class MyView: NSView {
override init(frame frameRect: NSRect) {
super.init(frame: frameRect)
let subView = MySubView(frame: NSRect(x: 0, y: frame.height/3, width: frame.width, height: frame.height/10))
self.addSubview(subView)
subView.autoresizingMask = [.width, .height]
subView.translatesAutoresizingMaskIntoConstraints = true
subView.trailingAnchor.constraint(greaterThanOrEqualTo: self.trailingAnchor).isActive = true
subView.leadingAnchor.constraint(greaterThanOrEqualTo: self.leadingAnchor).isActive = true
}
required init?(coder decoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class MySubView: NSView {
override func draw(_ dirtyRect: NSRect) {
let context = NSGraphicsContext.current!.cgContext
context.setStrokeColor(.black)
context.setFillColor(CGColor.init(red: 0.7, green: 0.7, blue: 0.7, alpha: 1))
context.setLineWidth(2)
let borderRect = CGRect(x: constant.sideMargin, y: 5, width: frame.width-2*constant.sideMargin, height: frame.height-10)
let roundedRect = CGPath.init(roundedRect: borderRect, cornerWidth: 5, cornerHeight: 5, transform: nil)
context.addPath(roundedRect)
context.fillPath()
context.strokePath()
context.addPath(roundedRect)
context.strokePath()
}
}
CodePudding user response:
As explained in the View Programming Guide, NSView
will scale and offset its contents if its bounds
is different from its frame
. Just set bounds
to {600, 500}
when frame
changes. The subviews of the view don't need constraints because their coordinates don't change.
MyView
setup code:
// frame changed notitication
self.postsFrameChangedNotifications = true
self.frameChangeObserver = NotificationCenter.default.addObserver(forName: NSView.frameDidChangeNotification,
object: self, queue: OperationQueue.main) { (note) in
self.setBoundsSize(NSSize(width:600, height: 500))
}
let subView = MySubView(frame: NSRect(x: 0, y: bounds.height/3, width: bounds.width, height: bounds.height/10))
// no constraints
subView.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(subView)