NSWindow animated blur & fade using CATransition and CIFilter: not working the first time? - cocoa

I try to apply a blur + saturation + fade effect on an NSWindow. Basically, when opening a sheet, I want the window behind the modal sheet to become blurred first.
Strangely, I can achieve this, but always at the second animation, never the first one. I've tried various combination of addSubview & addAnimation etc, but always failed so far.
Any idea? Here is my code:
self.contentView!.wantsLayer = true
let animation = CATransition()
animation.type = kCATransitionFade
animation.duration = 0.5
self.contentView!.layer?.addAnimation(animation, forKey: "layerAnimation")
let saturationFilter = CIFilter(name: "CIColorControls")!
saturationFilter.setDefaults()
saturationFilter.setValue(2, forKey: "inputSaturation")
let blurFilter = CIFilter(name: "CIGaussianBlur")!
blurFilter.setDefaults()
blurFilter.setValue(1.5, forKey:"inputRadius")
let coverView = NSView()
coverView.frame = self.contentView!.bounds
coverView.wantsLayer = true
coverView.layerUsesCoreImageFilters = true
coverView.autoresizingMask = [.ViewWidthSizable, .ViewHeightSizable]
coverView.layer!.opaque = false
coverView.layer!.backgroundColor = NSColor(white: 0.9, alpha: 0.5).CGColor
coverView.layer!.backgroundFilters = [saturationFilter, blurFilter]
self.contentView!.addSubview(coverView)

Related

iOS UIKit Multiline label and UITextView give incorrect visual height width

Im trying to setup a multiline set of views in a UIScrollView and ultimately a UIStackView. I found out I need to dynamically set the scrollviews height to the content to make it work. So when I went to debug why the values never worked I found that the labelType.frame.size is giving me a massive width or like the text was never wrapped, and the height is the height of the devices view.
If I change around the anchors for the content view I can make the text never line break fueling what I see as the CGSize numbers corresponding to the visual size
While the parent view does not even get a size which is why the default UIScrollView won't even work
content frame (0.0, 0.0)
uitextType (6791.0, 478.0)
labelType (6781.0, 460.5)
In this example I am testing both UITextView and UILabel, I know UITextView is not recommended but testing anyway, it has the same behavior with or without
Worse case I guess I can mix SwiftUI into the Uiview and carry on with work.
override func viewDidLoad() {
super.viewDidLoad()
}
override func loadView() {
super.loadView()
let content = UIView()
content.backgroundColor = UIColor.green
content.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(content)
let constraints2 = [
content.topAnchor.constraint(equalTo: self.view.topAnchor),
content.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
content.heightAnchor.constraint(equalTo: self.view.heightAnchor, constant: 0),
content.widthAnchor.constraint(equalTo: self.view.widthAnchor),
// content.bottomAnchor.constraint(equalTo: self.view.bottomAnchor),
// content.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
]
NSLayoutConstraint.activate(constraints2)
let uitextType = UITextView()
uitextType.backgroundColor = .clear
uitextType.text = lotatext
uitextType.isScrollEnabled = false
uitextType.translatesAutoresizingMaskIntoConstraints = false
content.addSubview(uitextType)
uitextType.topAnchor.constraint(equalTo: content.topAnchor).isActive = true
uitextType.leadingAnchor.constraint(equalTo: content.leadingAnchor).isActive = true
uitextType.heightAnchor.constraint(equalTo: content.heightAnchor).isActive = true
uitextType.widthAnchor.constraint(equalTo: content.widthAnchor).isActive = true // -40 2x?! lame
uitextType.font = UIFont.preferredFont(forTextStyle: .body)
uitextType.contentInsetAdjustmentBehavior = .automatic
uitextType.sizeToFit()
uitextType.contentMode = .topLeft
let labelType = UILabel()
labelType.backgroundColor = .clear
labelType.text = lotatext
labelType.numberOfLines = 0
labelType.lineBreakMode = .byWordWrapping
labelType.translatesAutoresizingMaskIntoConstraints = false
content.addSubview(labelType)
labelType.topAnchor.constraint(equalTo: content.topAnchor).isActive = true
labelType.leadingAnchor.constraint(equalTo: content.leadingAnchor).isActive = true
labelType.heightAnchor.constraint(equalTo: content.heightAnchor).isActive = true
labelType.widthAnchor.constraint(equalTo: content.widthAnchor).isActive = true
labelType.font = UIFont.preferredFont(forTextStyle: .body)
labelType.sizeToFit()
print("content frame \(content.frame.size)")
print("uitextType \(uitextType.frame.size)")
print("labelType \(labelType.frame.size)")
}
// testing text
let lotatext = """
Every day is taco ipsum tuesday. It’s a wonderful morning for breakfast tacos. 50 cent tacos! I’ll take 30. CARNE ASADA!! BARBACOA!! I’d have to say, those tacos are on fleek. It’s long been rumored that the chupacabra is really just a crazed man who’s local taco shop went out of business. Does guac cost extra? BARBACOA!! TACOS!! It’s raining tacos, from out of the sky, tacos, don’t even ask why. CARNE ASADA!!
50 cent tacos! I’ll take 30. I’d have to say, those tacos are on fleek. Let’s do a beef and a chicken, and one with both. I’ve been following that taco truck around all day. Make it a double there pal. It’s long been rumored that the chupacabra is really just a crazed man who’s local taco shop went out of business. I think I’ve overdosed on tacos. Tacos Al pastor/De Adobada are made of thin pork steaks seasoned with adobo seasoning, then skewered and overlapped on one another on a vertical rotisserie cooked and flame-broiled as it spins.
... snipped extras paragraphs
"""

UIStackView can I add it with autoresizing instead of autolayout

I've tried to place UIStackView inside UIScrollView with autoresizing masks .flexibleWidth, .flexibleHeight -> i.e. stretch horizontally/vertically to fill scroll view but stack view does not appear if I changed this to autolayout code with leading/trailing/bottom/top constraints then views appear correctly
toolbar.autoresizingMask = [.flexibleWidth, .flexibleHeight]
toolbar.backgroundColor = .clear
toolbar.axis = .horizontal
toolbar.distribution = .fill
toolbar.alignment = .fill
toolbar.spacing = 8
toolbarScroll.frame = bounds
toolbarScroll.autoresizingMask = [.flexibleHeight, .flexibleWidth]
toolbarScroll.showsHorizontalScrollIndicator = false
toolbarScroll.showsVerticalScrollIndicator = false
toolbarScroll.backgroundColor = UIColor.green
toolbarScroll.addSubview(toolbar)
Here is code that if added after addSubview then toolbar show in toolbarScroll
toolbar.translatesAutoresizingMaskIntoConstraints = false
toolbar.leadingAnchor.constraint(equalTo: toolbarScroll.leadingAnchor).isActive = true
toolbar.trailingAnchor.constraint(equalTo: toolbarScroll.trailingAnchor).isActive = true
toolbar.bottomAnchor.constraint(equalTo: toolbarScroll.bottomAnchor).isActive = true
toolbar.topAnchor.constraint(equalTo: toolbarScroll.topAnchor).isActive = true
You added toolbar to toolbarScroll, but you didn't give it a frame.
toolbarScroll.addSubview(toolbar)
// just to make sure...
toolbar.translatesAutoresizingMaskIntoConstraints = true
// give the toolbar stackView a frame
toolbar.frame = toolbarScroll.bounds

SceneKit - Stop continuously looping Collada animation

I am using SceneKit to load a Collada (.dae) file.
Whatever I have tried, the animation repeats in a continuous loop whereas I only want it play once and then stop.
Here's the loading code where sceneView is my SCNView:
//Load the scene and play
let scene = SCNScene(named: "Scenes.scnassets/test4.dae")
sceneView.scene = scene
sceneView.autoenablesDefaultLighting = true
sceneView.allowsCameraControl = true
The scene loads correctly and the animation plays on loading as required, but in a continuous loop.
I have tried removing all animations as follows:
sceneView.scene?.rootNode.removeAllAnimations()
but this seems to have no effect.
I have also tried retrieving all animations and setting the repeatCount = 1 or even to 0
let url = NSBundle.mainBundle().URLForResource("Scenes.scnassets/test4", withExtension: "dae")
let sceneSource = SCNSceneSource(URL:url!, options: nil)
let animationIndentifiers = sceneSource?.identifiersOfEntriesWithClass(CAAnimation)
if let ids = animationIndentifiers
{
for id in ids {
let animation: CAAnimation = sceneSource!.entryWithIdentifier(id as! String, withClass: CAAnimation.self)! as! CAAnimation
animation.repeatCount = 1
}
}
I have also tried this another way:
let keys = sceneView.scene!.rootNode.animationKeys() //get all the animation keys
if let theKeys = keys {
for key in theKeys {
let animation = sceneView.scene?.rootNode.animationForKey(key as! String)
animation?.repeatCount = 1
}
}
but again no luck.
I can find nothing in the Collada file itself that is obviously causing the animation to repeat. However, I notice that the .dae file icon on my Mac disk has a play button and clicking this also plays the animation within the icon on a continuous loop.
Update:
I now notice that in the code above I am setting the constant 'animation' attributes and this is not copied back to the actual scene nodes. Also, the only animation in the .dae file is in a child node. So here's my next attempt:
//Get the child nodes
let children = scene?.rootNode.childNodes
//Initialise a childNode counter
var childCount = 0
//Iterate through the child nodes
if let theChildren = children {
for child in theChildren {
let keys = child.animationKeys() //get all the animation keys
//Iterate through the animations
if let theKeys = keys {
for key in theKeys {
let animation = child.animationForKey(key as! String)
println("Initial Repeat count: \(animation.repeatCount)")
animation.repeatCount = 1
//Remove existing animation
scene?.rootNode.childNodes[childCount].removeAnimationForKey(key as! String)
//Add amended animation
scene?.rootNode.childNodes[childCount].addAnimation(animation, forKey: key as! String)
}
}
childCount++
}
}
There is actually only one animation attached to one child node. (I have also tried setting this using the the actual animation id string in place of 'key' above.)
The above shows Initial Repeat count: inf
and on checking afterwards it is indeed set to 1.
However, the animation still runs in an infinite loop :-(
Any help to resolve this would be much appreciated.
Further update
I have now created a new Collada file with simple animation using Maya and for some reason one of the trials attempted above actually works:
func sceneSetup() {
let scene = SCNScene(named: "Scenes.scnassets/test10.dae")
let children = scene?.rootNode.childNodes
var childCount = 0
if let theChildren = children {
for child in theChildren {
let keys = child.animationKeys() //get all the animation keys
if let theKeys = keys {
for key in theKeys {
let animation = child.animationForKey(key as! String)
animation.repeatCount = 1
scene?.rootNode.childNodes[childCount].removeAnimationForKey(key as! String)
scene?.rootNode.childNodes[childCount].addAnimation(animation, forKey: key as! String)
}
}
childCount++
}
}
sceneView.scene = scene
sceneView.autoenablesDefaultLighting = true
}
if anybody can explain that would be great!
Ah, but there's another problem!
Here's the start frame of the animation:
and here's the end frame where we want to end up:
But at the end of the animation the scene jumps back to the start view.
I have 'fixed' this by amending the animation so that frame 1 is copy of the final frame. This works and isn't noticeable but doesn't seem a very elegant solution.
For the animation jumping back to the first frame, the isAppliedOnCompletion value is what you are looking for.
animation.repeatCount = 1
animation.isAppliedOnCompletion = true
This will make sure that the animation pauses on the final frame.
I know this is an old question, but I came across this same problem and found a solution for the jumping back to the beginning issue. If you set the animation to not be removed on completion, the object should stay at the end location:
if let animation = child.animationForKey(child.animationKeys.first!) {
animation.repeatCount = 1
animation.removedOnCompletion = false
...
This works for me. It's based off your answer but will go through the node's entire tree and limit all of their animation counts to one.
Personally I found that if I missed any of the descendant nodes when manually traversing and setting the node animation counts to one, I would have a problem. This ensures that the node passed in itself, and all child nodes will only animate once, and then hold the model in place after finishing.
Use like this animateEntireNodeTreeOnce(mostRootNode: nodeYouImportedFromCollada).
func animateEntireNodeTreeOnce(mostRootNode node: SCNNode){
onlyAnimateThisNodeOnce(node)
for childNode in node.childNodes {
animateEntireNodeTreeOnce(mostRootNode: childNode)
}
}
func onlyAnimateThisNodeOnce(_ node: SCNNode) {
if node.animationKeys.count > 0 {
for key in node.animationKeys {
let animation = node.animation(forKey: key)!
animation.repeatCount = 1
animation.isRemovedOnCompletion = false
node.removeAllAnimations()
node.addAnimation(animation, forKey: key)
}
}
}
I Agree with #Phil Dudas and I like to add also this workaround to Prevent The Animation from Start By Default By Reducing Speed to Zero, When you set Speed to Zero it will remain at First state,
nodeAnimation.speed = 0
animation.repeatCount = 1
nodeAnimation.animation.isRemovedOnCompletion = false

NSBitmapImageRep.bitmapImageRepByConvertingToColorSpace() calling deprecated CGContextClear

I'm trying to use
NSBitmapImageRep.bitmapImageRepByConvertingToColorSpace(NSColorSpace.genericRGBColorSpace(), renderingIntent: NSColorRenderingIntent.Perceptual);
to convert an NSImage to a format that can be handled by openGL, and it works (unlike bitmapImageRepByRetaggingWithColorSpace()), but I get an error:
<Error>: The function ‘CGContextClear’ is obsolete and will be removed in an upcoming update. Unfortunately, this application, or a library it uses, is using this obsolete function, and is thereby contributing to an overall degradation of system performance.
which is sort of useless, because it's Apple's own code, and I can't seem to find an alternative to bitmapImageRepByConvertingToColorSpace(), or any note that it too is deprecated.
EDIT:
var image=NSImage(size: frame);
image.lockFocus();
//println("NSImage: \(image.representations)");
string.drawAtPoint(NSMakePoint(0.0,0.0), withAttributes: attribs);
var bitmap2=NSBitmapImageRep(focusedViewRect: NSMakeRect(0.0,0.0,frame.width,frame.height))?;
image.unlockFocus();
bitmap=bitmap2!.bitmapImageRepByConvertingToColorSpace(NSColorSpace.genericRGBColorSpace(), renderingIntent: NSColorRenderingIntent.Perceptual);
For some reason this question interested me. It looks like the solution is to lock focus on your NSImage, then use NSBitmapImageRep(focusedViewRect:) to create the NSBitmapImageRep.
I tried to recreate your situation (as I understand it) and then create an NSBitmapImageRep with the following code:
var img:NSImage = NSImage(size: NSMakeSize(200,200))
img.lockFocus()
let testString:NSString = "test String"
testString.drawAtPoint(NSMakePoint(10,10), withAttributes: nil)
img.unlockFocus()
println("img Description = \(img.description)")
I got the following description:
img Description = NSImage 0x608000067ac0 Size={200, 200} Reps=(
"NSCGImageSnapshotRep:0x61000007cf40 cgImage=CGImage 0x6100001a2bc0")
I then extended this code to:
var img:NSImage = NSImage(size: NSMakeSize(200,200))
img.lockFocus()
let testString:NSString = "test String"
testString.drawAtPoint(NSMakePoint(10,10), withAttributes: nil)
img.unlockFocus()
println("img Description = \(img.description)")
img.lockFocus()
var bitmapRep:NSBitmapImageRep = NSBitmapImageRep(focusedViewRect: NSMakeRect(0.0, 0.0, img.size.width, img.size.height))!
img.unlockFocus()
println("bitmap data planes = \(bitmapRep.bitmapData)")
println("bitmap pixels wide = \(bitmapRep.size.width)")
println("bitmap pixels high = \(bitmapRep.size.height)")
println("bits per sample = \(bitmapRep.bitsPerSample)")
println("samples per pixel = \(bitmapRep.samplesPerPixel)")
println("has alpha = \(bitmapRep.alpha)")
println("is planar = \(bitmapRep.planar)")
println("color space name = \(bitmapRep.colorSpace)")
println("bitmap format = \(bitmapRep.bitmapFormat)")
println("bytes per row = \(bitmapRep.bytesPerRow)")
println("bits per pixel = \(bitmapRep.bitsPerPixel)")
The output for the NSBitmapImageRep parameters was:
bitmap data planes = 0x000000010b6ff100
bitmap pixels wide = 200.0
bitmap pixels high = 200.0
bits per sample = 8
samples per pixel = 4
has alpha = true
is planar = false
color space name = Color LCD colorspace
bitmap format = C.NSBitmapFormat
bytes per row = 800
bits per pixel = 32
I've posted some new code below. I've created two NSBitMapImageRep just like your code (except that I used the method bitmapImageRepByRetaggingWithColorSpace), and I exported both of them to a PNG file. I got the same image in both PNG files.
But I'm wondering a couple of things. First, the difference in your two tests was not only the OS X version, but the hardware produced different color spaces. You should check to be sure bitmap2 is not nil.
Second, I'm wondering if OpenGL cares. If bitmap2 is not nil and you pass OpenGL bitmap2.bitmapData, does it work?
My new code (deleting most of the println):
var img:NSImage = NSImage(size: NSMakeSize(200,200))
img.lockFocus()
let testString:NSString = "test String"
let font:NSFont = NSFont(name: "AppleCasual", size: 18.0)!
let textStyle = NSMutableParagraphStyle.defaultParagraphStyle().mutableCopy() as NSMutableParagraphStyle
textStyle.alignment = NSTextAlignment.LeftTextAlignment
let textColor:NSColor = NSColor(calibratedRed: 1.0, green: 0.0, blue: 1.0, alpha: 1.0)
let attribs:NSDictionary = [NSFontAttributeName: font,
NSForegroundColorAttributeName: textColor,
NSParagraphStyleAttributeName: textStyle]
testString.drawAtPoint(NSMakePoint(0.0, 0.0), withAttributes: attribs)
img.unlockFocus()
println("img Description = \(img.description)")
img.lockFocus()
var bitmapRep:NSBitmapImageRep = NSBitmapImageRep(focusedViewRect: NSMakeRect(0.0, 0.0, img.size.width, img.size.height))!
img.unlockFocus()
let pngPath:String = "TestStringBeforeChange.png"
let imageProps = [NSImageCompressionFactor: NSNumber(float: 1.0)]
let outputImageData = bitmapRep.representationUsingType(NSBitmapImageFileType.NSPNGFileType, properties: imageProps)
let fileSavePanel = NSSavePanel()
fileSavePanel.nameFieldStringValue = pngPath
var savePanelReturn = fileSavePanel.runModal()
if savePanelReturn == NSFileHandlingPanelOKButton
{
var theFileURL = fileSavePanel.URL
var saveStatus = outputImageData?.writeToURL(theFileURL!, atomically: true)
}
let bitmapRep2 = bitmapRep.bitmapImageRepByRetaggingWithColorSpace(NSColorSpace.genericRGBColorSpace())
let pngPath2:String = "TestStringAfterChange.png"
let outputImageData2 = bitmapRep2!.representationUsingType(NSBitmapImageFileType.NSPNGFileType, properties: imageProps)
let fileSavePanel2 = NSSavePanel()
fileSavePanel2.nameFieldStringValue = pngPath2
var savePanel2Return = fileSavePanel2.runModal()
if savePanel2Return == NSFileHandlingPanelOKButton
{
var theFileURL2 = fileSavePanel2.URL
var saveStatus2 = outputImageData2?.writeToURL(theFileURL2!, atomically: true)
}
Sorry for all the posts, but I don't want to let this go, and it's interesting.
I created 3 PNG files: the first using the original NSBitmapImageRep, a second using an NSBitmapImageRep created with bitmapImageRepByRetaggingWithColorSpace and a third using an NSBitmapImageRep created with bitmapImageRepByConvertingToColorSpace (I, of course, got the system warning when I used this function).
Looking at these three PNG files in Preview's Inspector, they were all RGB color models; only the colorSynch profile for the first file was different from that of the second and third file. So I thought maybe there's no difference in the bitmapData among these files.
I then wrote some code to compare the bitmapData:
var firstRepPointer:UnsafeMutablePointer<UInt8> = bitmapRep.bitmapData
var secondRepPointer:UnsafeMutablePointer<UInt8> = bitmapRep2!.bitmapData
var firstByte:UInt8 = 0
var secondByte:UInt8 = 0
for var i:Int = 0; i < 200; i++
{
for var j:Int = 0; j < 200; j++
{
for var k:Int = 0; k < 4; k++
{
memcpy(&firstByte, firstRepPointer, 1)
firstRepPointer += 1
memcpy(&secondByte, secondRepPointer, 1)
secondRepPointer += 1
if firstByte != secondByte {println("firstByte = \(firstByte) secondByte = \(secondByte)")}
}
}
}
As I expected, there were no differences when I compared the bitmapData from the original bitmap rep to the bitmapData from the bitmap rep created with bitmapImageRepByRetaggingWithColorSpace.
It got more interesting, however when I compared the bitmapData from the original bitmap rep to the data from the bitmap rep created with bitmapImageRepByConvertingToColorSpace.
There were lots of differences in the data. Small differences, but lots of them.
Even though there were no differences I could see in the PNG files, the underlying data had been changed by bitmapImageRepByConvertingToColorSpace.
All that said, I think you should be able to pass .bitmapData from any of these bitmap reps to an OpenGL texture. The data from the bitmap rep created with bitmapImageRepByConvertingToColorSpace might create a texture that looks different, but it should be valid data.

Creating UI element won't let change position

I tried this:
#IBAction func test(sender : AnyObject){
let height:CGFloat = 44
var tableFrame:CGRect = tableView.frame;
var fieldFrame = CGRect()
fieldFrame.origin = tableFrame.origin
fieldFrame.size.height = height
fieldFrame.size.width = tableFrame.size.width
var textField = UITextField(frame: fieldFrame)
textField.backgroundColor = UIColor(white: 0, alpha: 1)
view.addSubview(textField)
tableFrame.size.height = tableFrame.size.height - height
tableFrame.origin.y = tableFrame.origin.y + height
tableView.frame = tableFrame
}
When running, black field appears, but table won't move nor change size. Removing line
view.addSubview(textField)
allows table to change size and move, but, obviously, no field appears. What is the problem?
your textfield is actually on top of tableview. Your touch actions are probably going to the text fieldview and not to the tableview

Resources