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")
}
Related
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.
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.
In my swift app I've a collection view and I want to creare a stretchable header view like this in table view: https://medium.com/if-let-swift-programming/how-to-create-a-stretchable-tableviewheader-in-ios-ee9ed049aba3
You already answered your question yourself with that article link, unless I miss something.
I will copy & paste for you and others that may have the same question, if it helps, because it even ships with a github link (kudos to Abhimuralidharan # Medium):
Create a tableview with the basic datasource and delegate methods which are required to simply load the table with some data.
Set the tableview’s contentInset property:
tableView.contentInset = UIEdgeInsetsMake(300, 0, 0, 0)
Here, I set the top value as 300 which is a calculated number which I will set as the initial normal height for the header imageview. Now, that we set the contentInset , the tableview’s frame will start at (0,0) and the first cell will start at (0,300).
Now, create an imageview with height 300 and add it to the current View above the tableview.
imageView.frame = CGRect(x: 0, y: 0, width: UIScreen.main.bounds.size.width, height: 300)
imageView.image = UIImage.init(named: “poster”)
imageView.contentMode = .scaleAspectFill
imageView.clipsToBounds = true
view.addSubview(imageView)
Then, add the following code in the scrollview delegate method scrollViewDidScroll which gets called every time the tableview is scrolled.
func scrollViewDidScroll (_ scrollView: UIScrollView) {
let y = 300 — (scrollView.contentOffset.y + 300)
let height = min(max(y, 60), 400)
imageView.frame = CGRect(x: 0, y: 0, width: UIScreen.main.bounds.size.width, height: height)
}
Compile and run the code. Full source code is available in github.
I am trying to set up zoom for a game where you click on a button to zoom in, and another to zoom out (which will be part of the UI).
I am having problems getting the input of the buttons to the code. I can read that the button is not pressed, but I cannot figure out how to read if it is pressed.
Looking through the API reference gave me the idea to try using the "state" of the button, but it always returns 0, even when the button is pressed. This could be because the update function does not update as fast as the button's state changes.
Here is my code:
class GameScene: SKScene {
override func sceneDidLoad() {
cam = SKCameraNode()
cam.xScale = 1
cam.yScale = 1
self.camera = cam
self.addChild(cam)
cam.position = CGPoint(x: 0, y: 0)
setUpUI()
}
func setUpUI(){
let button1Rect = CGRect(x: 20, y: 80, width: 80, height: 40)
let zoomIn = NSButton(frame: button1Rect) //place a button in the Rect
zoomIn.setButtonType(NSMomentaryPushInButton)
zoomIn.title = "Zoom In"
zoomIn.alternateTitle = "Zoom In"
zoomIn.acceptsTouchEvents = true
view?.addSubview(zoomIn) //put button on screen
let button2Rect = CGRect(x: 20, y: 30, width: 80, height: 40)
let zoomOut = NSButton(frame: button2Rect) //place a button in the Rect
zoomOut.setButtonType(NSMomentaryPushInButton)
zoomOut.title = "Zoom Out"
zoomOut.alternateTitle = "Zoom Out"
zoomOut.acceptsTouchEvents = true
view?.addSubview(zoomOut) //put button on screen
if zoomIn.state == 1{ //this loop is never true no matter how many times I press the button
print("zoom")
}
}
}
I did cut out some of the code involving the mouse as it is not relevant.
Answer is from TheValyreanGroup.
Simply define the button outside of the setUpUI() function (outside of the GameScene or within it works, outside of the GameScene is easier).