I am new to Swift for mac and currently I have an NSMenu, which autoresizes in width, when the menu items are long. However, I would like to set a max width, either for NSMenu or NSMenuItem. The NSMenu is created in created programmatically.
Currently, I have tried the setFrameSize or setBoundsSize, but they do nothing or trying setting an NSView with a new rect, but erases all the options that existed on the menu
var menuItems = myMenu.items
menuItems.forEach { item in
let title = item.title
//let autoresizingMask: NSView.AutoresizingMask = [.minXMargin, .minYMargin, .maxXMargin, .maxYMargin]
//item.view?.setBoundsSize(NSSize(width: 100, height: 32)) //= NSMakeRect(0, 0, 100, 32)
item.view?.setFrameSize(NSSize(width: 100, height: 32))
item.view?.needsDisplay = true
or
let v = NSView(frame: NSMakeRect(0, 0, 100, 32))
var menuItems = myMenu.items
menuItems.forEach { item in
item.view = v
}
I am not sure if changing the view is the right thing to do or if I am overwriting the existing view that way. Is there a more straight way to set max width for NSMenu?
EDIT:
The text is already automatically truncated, but still exceeds the preferable max Width limit.
Related
I developed this app using Xcode 10.1 (Swift 4.2 I believe) and toolbar looks good:
But I ported in over to my Xcode 11.6 (Swift 5.2 I believe), now the right-most item on the toolbar is no longer on the far right:
Before the port, I added that plus button - the checkbox (Show Hidden) was one of the first items and was never changed since.
Also the buttons function as expected - their actions fire OK.
Note: This app uses no Storyboards or IB things.
Since my code in split over several files (with lots of unrelated code), I will give an overview.
class ToolbarController: NSObject, NSToolbarDelegate {
let toolbar = NSToolbar(identifier: NSToolbar.Identifier("toolbar"))
lazy var toolbarItem1: NSToolbarItem = {
let toolbarItem = NSToolbarItem(itemIdentifier: NSToolbarItem.Identifier(rawValue: "Btn1"))
let button = NSButton(frame: NSRect(x: 0, y: 0, width: 40, height: 1))
button.target = self
...
toolbarItem.view = button
return toolbarItem
}() // defined as "lazy" to allow "self"
var toolbarItemSpace: NSToolbarItem = {
let toolbarItem = NSToolbarItem(itemIdentifier: NSToolbarItem.Identifier.space)
return toolbarItem
}()
...
var toolbarItemFlexSpace: NSToolbarItem = {
let toolbarItem = NSToolbarItem(itemIdentifier: NSToolbarItem.Identifier.flexibleSpace)
return toolbarItem
}()
lazy var toolbarItemShowHidden: NSToolbarItem = {
let toolbarItem = NSToolbarItem(itemIdentifier: NSToolbarItem.Identifier(rawValue: "Checkbox"))
let box = NSBox(frame: NSRect(x: 0, y: 0, width: 120, height: 40))
let button = NSButton() // put in NSBox to use isolated action
...
button.target = self
box.contentView = button
box.sizeToFit() // tightly fit the contents (button)
toolbarItem.view = box
return toolbarItem
}()
lazy var tbItems: [NSToolbarItem] = [toolbarItem1, ...]
That class defines the toolbar, add the items, implements all those toolbar (delegate) methods and makes it the delegate. My NSWindowController then set it to the application toolbar.
Can anyone see an issue with my code that causes the above?
Otherwise, is this a bug?
Had the same problem with Swift 5.0 ... even though the documentation says minSize and maxSize are deprecated, I solved this like:
let toolbarItem = NSToolbarItem(itemIdentifier: NSToolbarItem.Identifier.flexibleSpace)
toolbarItem.minSize = NSSize(width: 1, height: 1)
toolbarItem.maxSize = NSSize(width: 1000, height: 1) //just some large value
return toolbarItem
Maybe this works as well for Swift 5.2
Found a way without living w/ the warning & not using the deprecated properties.
We create a transparent subview for the NASToolbarItem with the min / max constraints in advance, so the system has a way to calc the min and max size itself.
let toolbarItem = NSToolbarItem(itemIdentifier: NSToolbarItem.Identifier.flexibleSpace)
// view to be hosted in the flexible space toolbar item
let view = NSView(frame: CGRect(origin: .zero, size: CGSize(width: MIN_TOOLBAR_ITEM_W, height: MIN_TOOLBAR_H)))
view.widthAnchor.constraint(lessThanOrEqualToConstant: MAX_TOOLBAR_ITEM_W).isActive = true
view.widthAnchor.constraint(greaterThanOrEqualToConstant: MIN_TOOLBAR_ITEM_W).isActive = true
// set the view and return
toolbarItem?.view = view
return toolbarItem
I have view with RPSystemBroadcastPickerView view. In documentation apple shows example with assigning frame to this view like so:
https://developer.apple.com/documentation/replaykit/rpsystembroadcastpickerview?language=objc
When set frame + constraints, works as expected:
picker = RPSystemBroadcastPickerView(frame: CGRect(x: 0, y: 0, width: 50, height: 50))
but if i do initialize RPSystemBroadcastPickerView like this:
picker = RPSystemBroadcastPickerView()
subviews doesn't display properly.
Constraints for both cases:
picker.translatesAutoresizingMaskIntoConstraints = false
pickerViewContainerView.addSubview(picker)
picker.widthAnchor.constraint(equalTo: pickerViewContainerView.widthAnchor).isActive = true
picker.heightAnchor.constraint(equalTo: pickerViewContainerView.heightAnchor).isActive = true
picker.leadingAnchor.constraint(equalTo: pickerViewContainerView.leadingAnchor).isActive = true
picker.topAnchor.constraint(equalTo: pickerViewContainerView.topAnchor).isActive = true
Do i have to set initial frame for this view? Because usually if you create and position view using constraints you don't have to assign initial frame.
Can somebody explain this behavior please?
Thanks.
Yes, you have to set the initial frame. You can check the width and height when you instantiate without initial frame.
picker = RPSystemBroadcastPickerView()
print("Height : \(picker.frame.height)") // this will print as 0.0
print("Width : \(picker.frame.width)") // this will print as 0.0
Since, the picker view is of height and width 0.0 x 0.0, it is not visible and is not working
With the initial frame the width and height prints 50.0 x 50.0 and visible.
picker = RPSystemBroadcastPickerView(frame: CGRect(x: 0, y: 0, width: 50, height: 50))
print("Height : \(picker.frame.height)") // this will print as 50.0
print("Width : \(picker.frame.width)") // this will print as 50.0
That is why in the Apple Developer Documentation they suggested to use initial frame.
i have created a subview with a lot sliders in it
var sliderArea = UIView()
sliderArea = UIView(frame: CGRect(x: 300, y: 400, width: 500, height: 100)
view.addSubview(sliderArea)
mySlider1 = setUpSlider(sliderNr: 1, ypos: 30)
mySlider2 = setUpSlider(sliderNr: 2, ypos: 30)
mySlider3 = setUpSlider(sliderNr: 3, ypos: 30)
sliderArea.addSubview(mySlider1)
sliderArea.addSubview(mySlider2)
sliderArea.addSubview(mySlider3)
i have a lot of subviews similar to the "sliderArea" to be able to change my sliders quickly while the layout adopts automatically
now i need to know where the absolute position of each slider is to place buttons on top of it. i need to have all this buttons inside an extra view on top of it all. any ideas? thank you
You should convert your slider frame to window's coordinate system using this method of CGRect:
https://developer.apple.com/documentation/uikit/uiview/1622504-convert
Just pass nil as second parameter and it will return you an absolute frame.
I have an NSButton, created programatically. within said button, I have an NSImage that serves as an icon:
let button = NSButton(frame: NSRect(x: 10, y: (200 + (50 * id)), width: 100, height: 50))
button.action = #selector(self.switchToView)
let img = NSImage(named: "wob_icon")
button.image = img
What I'm trying to do is get the image 10pt. from the left side of the button, while centred vertically. So far, the image shows up centered horizontally as well as vertically, but since it doesn't seem like I'm able to define a frame or something like that, I can't really move it.
Is there a way to move the image within it's parent (the button)?
Thanks.
Maybe you could use the imagePosition property on a NSButton? (documented here).
It uses an enum of type NSCellImagePosition (documented here) and allows you to set the image to the left of the text, to the right of the text, above the text, below the text and so on.
You still won't have the opportunity to align things pixel perfect but if you can live with that, then imagePosition seems like the way to go.
Here is how to use it:
let button = NSButton(frame: NSRect(x: 10, y: 10, width: 100, height: 50))
button.action = #selector(self.switchToView)
let img = NSImage(named: "wob_icon")
button.image = img
button.imagePosition = .imageLeft
button.title = "Look left :)"
And that gives me this stunning UI:
Hope that helps you.
Using NSAnimationContext.runAnimationGroup(_:_:) as demonstrated in the NSAnimationContext documentation to animate both the frame origin and size works as expected for some view types (including NSImageView). However, it does not work as expected for an NSButton unless I add an explicit frame size change after the animation
Animating frame size for NSImageView
The following works as expected for an NSImageView. It is moved to the origin, and resized to 200x200:
NSAnimationContext.runAnimationGroup({(let context) -> Void in
context.duration = 2.0
// Get the animator for an NSImageView
let a = self.theImage.animator()
// Move and resize the NSImageView
a.frame = NSRect(x: 0, y: 0, width: 200, height: 200)
}) {
print("Animation done")
}
Animating frame size for NSButton
When performing the same with an NSButton, the button will move but not resize:
NSAnimationContext.runAnimationGroup({(let context) -> Void in
context.duration = 2.0
// Get the animator for an NSButton
let a = self.button.animator()
// Move and resize the NSImageView
a.frame = NSRect(x: 0, y: 0, width: 200, height: 200)
}) {
print("Animation done")
}
However, if I add the following line of code to the end, after all of the animation code, it works as expected!
self.button.frame = NSRect(x: 0, y: 0, width: 200, height: 200)
The final, working listing for NSButton is:
NSAnimationContext.runAnimationGroup({(let context) -> Void in
context.duration = 2.0
// Get the animator for an NSButton
let a = self.button.animator()
// Move and resize the NSImageView
a.frame = NSRect(x: 0, y: 0, width: 200, height: 200)
}) {
print("Animation done")
}
self.button.frame = NSRect(x: 0, y: 0, width: 200, height: 200)
I'm not looking a gift horse in the mouth here, but I don't understand why this is required for NSButton, or even what makes it work. Can anyone explain why explicitly setting the frame of the NSButton after the animation code makes the animation work?
I suspect this has something to do with implicit autolayout constraints being generated at runtime. After modifying the frame, the autolayout simply reverts it back to the original size.
I abandoned the original approach in favor of the following:
Create width and/or height constraints in Interface Builder. I use the default priority of 1000 (constraint is mandatory).
Create an outlet for the width and/or height NSLayoutConstraint. This was tricky in IB: I had to double-click the constraint in the measurements tab of the inspector which then opened the constraint object in the inspector. Then you can select the connections tab and connect the outlet.
In my NSViewController subclass I use anchors to define the new height or width: theWidthConstraint.constant = 200 followed by self.view.needsUpdateConstraints = true
This approach is much cleaner and more compatible with the autolayout system. In addition, it makes it easy to animate the entire layout change that results from the new autolayout:
NSAnimationContext.runAnimationGroup({(let context) -> Void in
context.duration = 1.0
self.theWidthConstraint.animator().constant = 200
// Other constraint modifications can go here
}) {
print("Animation done")
}