NSWindow - scale all content when resizing - swift

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 set subView.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()
}
}

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)

Related

Weird blurred SKShapeNode

I am trying to start with the basics of SpriteKit and draw a rounded rectangle
import Foundation
import SpriteKit
class UINode: SKShapeNode {
override init() {
super.init()
self.path = UIBezierPath(roundedRect: CGRect(x: -5, y: -2, width: 10, height: 4), cornerRadius: 1).cgPath
self.position = CGPoint(x: 0, y: 0)
self.fillColor = UIColor.red
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
The respective Scene containing that SKShapeNode contains a SKCameraNode, which can be moved and scaled for panning/zooming. That works fine.
However the output of that Shape is very strange, as you can see in the attachment. This is the result of the above code.
I have no idea what's going on. If I reduce the cornerRadius to zero, I get a rectangular shape with the same strange white border and artifacts. But I except a simple rounded Rect without any blurring, second color or artifacts (thin lines)
What am I doing wrong?
Rendering result

UIView incorrect frame on #3x devices

I have a really weird bug on #3x devices where a UIView is visually incorrectly sized.
I insist on the visually word because when I print out the frame of that particular view, everything is correct.
On #2x devices, everything looks fine.
My view hierarchy is really simple. I have a view (view B) inside another one (view A).
View B is centered in its superview (view A).
Really simple right?
Frame of view B should be:
CGRect(x: 8.5, y: 15.5, width: 19.0, height: 5.0)
If we scale it with a factor of 3 (to obtain its real dimensions on #3x devices), we should have a rectangle with the following frame:
CGRect(x: 25.5, y: 46.5, width: 57.0, height: 15.0)
When testing on a #3x device, visually, the origin.x of the view is 26px (instead of 25.5), and its width is 56px (instead of 57px).
Here is the code:
class ViewController: UIViewController {
private static let centeredViewSize = CGSize(width: 36, height: 36)
private let centeredView = UIView(frame: .zero)
private let rectangleView = UIView(frame: .zero)
// MARK: - View Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
self.rectangleView.clipsToBounds = true
self.rectangleView.backgroundColor = UIColor.purple
self.centeredView.backgroundColor = UIColor.red
self.centeredView.addSubview(self.rectangleView)
self.view.addSubview(self.centeredView)
}
// MARK: - Layout
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
self.centeredView.frame.size = ViewController.centeredViewSize
self.centeredView.center = self.view.center
let rectangleViewSize = CGSize(width: 19, height: 5)
let rectangleViewHorizontalOrigin = self.centeredView.bounds.midX - (rectangleViewSize.width / 2)
let rectangleViewVerticalOrigin = self.centeredView.bounds.midY - (rectangleViewSize.height / 2)
let rectangleViewOrigin = CGPoint(x: rectangleViewHorizontalOrigin, y: rectangleViewVerticalOrigin)
self.rectangleView.frame = CGRect(origin: rectangleViewOrigin, size: rectangleViewSize)
}
}
This whole issue seems to come from the horizontal origin.
If I round it like this:
let rectangleViewHorizontalOrigin = (self.centeredView.bounds.midX - (rectangleViewSize.width / 2)).rounded()
The issue is gone. But that's not a solution. I want that view to be perfectly centered in its superview.
How can I fix this?
I created a demo project so you can try it out.
I run your code. Your frames are perfectly centred at any device.
Only two things I've changed.
1st in:
override func viewDidLoad() {
super.viewDidLoad()
// Change the order of adding subviews
self.view.addSubview(self.centeredView)
self.centeredView.addSubview(self.rectangleView)
}
2nd:
I use override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
}
instead override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
}
If something will go wrong, please edit your question with new screenshots.
Here is my console output.

Swift creating a line inside a view that the user can resize

How can I create a simple line that I can control its originating point, but let the user determine where it ends?
I have a UIView on my storyboard and on top of this I want to insert a line. The line would be restricted to be within this particular view and would start vertically centered and from the left side.
I have buttons with a - and a + to allow the user to increase its length or decrease it.
I suppose the easiest method will be to a add another UIView (call it lineView) inside your main view, set it's width to whatever you want the width of your line to be and hook it up with an #IBOutlet. Then in your #IBAction methods for - and +, change the height of lineView
I found the answer here, How to draw a line between two points over an image in swift 3?
class LineView : UIView {
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = UIColor.init(white: 0.0, alpha: 0.0)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func draw(_ rect: CGRect) {
if let context = UIGraphicsGetCurrentContext() {
context.setStrokeColor(UIColor.blue.cgColor)
context.setLineWidth(3)
context.beginPath()
context.move(to: CGPoint(x: 5.0, y: 5.0)) // This would be oldX, oldY
context.addLine(to: CGPoint(x: 50.0, y: 50.0)) // This would be newX, newY
context.strokePath()
}
}
}
let imageView = UIImageView(image: #imageLiteral(resourceName: "image.png")) // This would be your mapView, here I am just using a random image
let lineView = LineView(frame: imageView.frame)
imageView.addSubview(lineView)

How to use NSScroller in a custom NSView

I have a custom NSView with two NSClipViews. I want to add a vertical scroller for each clipview. The snippet below shows a NSScroller, but it is not drawing any knob or buttons. Interaction is also not possible.
NSScroller is a small class, so I am not sure what I am missing here.
required init?(coder: NSCoder)
{
super.init(coder: coder)
let width = NSScroller.scrollerWidthForControlSize(NSControlSize.RegularControlSize, scrollerStyle: NSScrollerStyle.Overlay)
let scroller = NSScroller(frame: NSRect(x: 10, y: 10, width: width, height: 500))
self.addSubview(scroller)
scroller.doubleValue = 0.5
scroller.knobProportion = 0.1
scroller.needsLayout = true //no effect
scroller.needsDisplay = true //no effect
}
The missing line:
scroller.enabled = true

How to add UIView on top of SKView?

I wanted to add a Custom NSView on top of and SKView. However, it is never showing up. Have no clue what is happening...
class GameScene: SKScene {
override func didMoveToView(view: SKView) {
//Trying to add Custom View
let smallView = NSView(frame: NSRect(origin:CGPoint(x: 100,y: 100), size: CGSize(width: 200, height: 200)))
let layer = CALayer()
layer.backgroundColor = CGColorCreateGenericRGB(1.0, 0.0, 0.0, 0.4)
smallView.layer = layer
view.superview!.addSubview(smallView)
}
}
Could anyone who could give me an insight into what is happening here? I even tried adding some NSLabels to the MainStory board. Nothing happens.
I wanted to draw some thing with Coregrapghics and Show on top of the SKScene.But, I can't move forward.
https://github.com/cocoBavan/NSViewInSpriteKit
Try to add UIView from your ViewController, instead of adding it from SKScene.
In your VC add function (for example), create UIView, and then just add it as a subview of a view, like so:
let myView = UIView(frame: CGRect(x: 0, y:0, width: 100, height: 100))
self.view.addSubview(myView)
It will add a new view above existing view, so your GameScene would be below new UIView.