CALayer can't be seen - swift

I'm trying to learn swift. So far, my code runs, but it doesn't show a CAlayer I create:
class AppDelegate: NSObject, NSApplicationDelegate {
var window: NSWindow!
var mainview: NSView!
var redLayer: CATextLayer!
func applicationDidFinishLaunching(aNotification: NSNotification) {
self.mainview = NSView(frame: NSRect(x:0,y:0,width:300,height:300))
self.window = NSWindow(contentRect: NSMakeRect(0, 0, 300, 300),
styleMask: NSTitledWindowMask | NSClosableWindowMask |
NSMiniaturizableWindowMask,
backing: .Buffered, `defer`: false)
self.window.contentView!.addSubview(self.mainview)
self.window.makeKeyAndOrderFront(self.window)
self.window.makeMainWindow()
self.redLayer = CATextLayer()
self.redLayer.string = "test"
self.redLayer.frame = self.mainview.bounds
self.redLayer.bounds = CGRect(x:50,y:50,width:300,height:300)
self.redLayer.position = CGPoint(x: 50, y: 50)
self.redLayer.backgroundColor = CGColorCreateGenericRGB(1.0, 0.0, 0.0, 1.0)
self.redLayer.foregroundColor = CGColorCreateGenericRGB(0.0, 0.0, 0.0, 1.0)
self.redLayer.hidden = false
self.mainview.layer = self.redLayer
}
func applicationWillTerminate(aNotification: NSNotification) {
// Insert code here to tear down your application
}}
the code gives a gray empty window. but I want it to show a colored window with a line of text saying "test", but I can't see these stuff.
I tried multiple ways, I can't fix this issue.

In OS X, Core Animation support (CALayer) is not enabled automatically. You have to manually enable it. For a programmatically created view, use the wantsLayer property.
self.mainview.wantsLayer = true

Related

NSWindowController NSHostingView based contentView: Frame size only updated with delay

I have a simple setup here representative of what I do in my MacOS app:
// AppDelegate.swift
import Cocoa
#main
class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ aNotification: Notification) {
TestWindow()
}
func applicationWillTerminate(_ aNotification: Notification) {}
func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool {
return true
}
}
// BareView.swift
import SwiftUI
struct BareView: View {
#ObservedObject var testState: TestState
var body: some View {
Color.white.frame(width: testState.width, height: testState.height)
}
}
// TestWindow.swift
import Foundation
import AppKit
import SwiftUI
class TestState: ObservableObject {
#Published var width: CGFloat = 100
#Published var height: CGFloat = 100
}
class TestWindow: NSWindowController {
let testState = TestState()
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
init() {
super.init(window: NSWindow(
contentRect: .zero,
styleMask: [ .borderless ],
backing: .buffered,
defer: false))
self.window!.contentView = NSHostingView(rootView: BareView(testState: testState))
self.window!.setFrameOrigin(.init(x: NSScreen.main!.frame.width / 2, y: NSScreen.main!.frame.height / 2))
self.window!.makeKeyAndOrderFront(nil)
NSEvent.addLocalMonitorForEvents(matching: [.mouseMoved]) { event in
self.testState.width += 10
self.testState.height += 10
self.window!.makeKeyAndOrderFront(nil)
// behaviour only as expected if this line is added:
// self.window!.setContentSize(.init(width: self.testState.width, height: self.testState.height))
print("contentSize: \(self.testState.width)")
print("viewFrame: \(self.window!.contentView!.frame)")
return nil
}
}
}
The size of the SwiftUI View inside the NSHostingView is updated based on an EventHandler. The SwiftUI View is actually resized and increases its size, but the contentViews frame size is not directly notified about this change. This happens one iteration later (after the window has moved?).
So the console prints the following:
contentSize: 110.0
viewFrame: (0.0, 0.0, 100.0, 100.0)
contentSize: 120.0
viewFrame: (0.0, 0.0, 110.0, 110.0)
Once I manually update the contentViews size with setContentSize(_:) function the console output behaves as expected:
contentSize: 110.0
viewFrame: (0.0, 0.0, 110.0, 110.0)
contentSize: 120.0
viewFrame: (0.0, 0.0, 120.0, 120.0)
So it seems like Swift is recalculating the window only after the code has returned. Since the SwiftUI view has increased in size, but the Window does not know about this, the origin is not valid. This creates a bug in my app.
I can solve this problem by calling setContentSize(_:), but the information about its actual size is contained in a leave view all the way down the tree. I would need to use an ObservableObject to communicate the calculated size back to the NSWindowController.
Can someone explain to me if this is expected behaviour and if so, what happens here? There must be some kind of "window redraw cycle" that I don't know about.
If you're looking to capture window resizing as it happens, you could try:
.onReceive(NotificationCenter.default.publisher(for: NSWindow.didResizeNotification)) { newValue in
if let size = window?.frame.size {
windowWidth = size.width
windowHeight = size.height
}
}

Opening an NSWIndow / NSView on a connected display

Environment: macOS Big Sur 11.5.2 / Swift 5.4
Target Platform: macOS
I came across this discussion while searching for how to open a new NSWindow / NSView on a connected display.
I converted the obj-c code to Swift, but I cannot get it to work. I have the method set up inside an IBAction and I linked the action to a button I placed in the view titled "Show External Display."
What am I doing wrong?
import Cocoa
class ViewController: NSViewController {
var externalDisplay: NSScreen?
var fullScreenWindow: NSWindow?
var fullScreenView: NSView?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
#IBAction func showExternalDisplayButtonClicked(sender: Any?) {
// Open a window on the external display if present
if NSScreen.screens.count > 1 {
externalDisplay = NSScreen.screens.last
let externalDisplayRect = externalDisplay!.frame
fullScreenWindow = NSWindow(contentRect: externalDisplayRect, styleMask: .borderless, backing: .buffered, defer: true, screen: externalDisplay)
fullScreenWindow!.level = .normal
fullScreenWindow!.isOpaque = false
fullScreenWindow!.hidesOnDeactivate = false
fullScreenWindow!.backgroundColor = .red
let viewRect = NSRect(x: 0, y: 0, width: externalDisplay!.frame.width, height: externalDisplay!.frame.height)
fullScreenView = NSView(frame: viewRect)
fullScreenWindow!.contentView = fullScreenView
fullScreenWindow!.makeKeyAndOrderFront(self)
}
}
}
Why do you make styleMask: .borderless? Docs say a borderless window can't become key or main so makeKeyAndOrderFront fails.
If you make a normal window like this:
let mask: NSWindow.StyleMask = [.titled, .closable, .miniaturizable, .resizable]
fullScreenWindow = NSWindow(contentRect: externalDisplayRect, styleMask: mask, backing: .buffered, defer: true, screen: externalDisplay)
it opens on the last screen.

Swift Scroll View with slider and MIDI

Im using Scroll View to scroll a picture. I'm using a slider 1 - 26 to retrieve CGPoint from an Array for x axis points. Everything behaves as expected and the image is smooth in transition.
I also set up a button that when pressed count from 1 - 26 and loops to retrieve CGPoint from an Array for x axis points. Everything behaves as expected and the image is smooth in transition.
I have also set up MIDI that sends data 1 - 26 based on velocity this is sent to the slider, and the slider moves as expected. I also using MIDI to retrieve CGPoint from an Array for x axis points. Unfortunately although it wants to scroll the following pictures shows what happens you only get to see 4 images as it scrolls and you can tell by the moving slider when they appear
I'm very confident that the Midi is working correctly as i use it all the time to read from arrays. I have also tried triggering the button from Midi a kind of virtual press. This works as expected but shows the same as triggering directly from midi. It seems as though everything is fine as long as the scroll originates from a IB component.
Under Midi control the scroll bar also stutters but if i set
clipView.wantsLayer = true
The picture does not scroll under midi but the scroll bar moves as expected. Even though the slider and the button work as expected.
I have also tried all the things commented out in the playMidi. As well trying all the different parameters in the IB section.
I am at a point where i really could do with some help please.
Here is my code which hopefully also explains what i have tried.
I have also tried various
needsDisplay = true
on all views
Xcode 8 Swift3 OSX not iOS
Thank you for you time
import Cocoa
import CoreMIDI
class MainWindowController: NSWindowController {
static var subWindowController: MainWindowController!
var pointArray:[Double] = [99.00,0.00,40.00,80.00,120.00,160.00,200.00,240.00,280.00,320.00,360.00,400.00,440.00,480.00,520.00,560.00,600.00,640.00,680.00,720.00,760.00,800.00,840.00,880.00,920.00,960.00,1000.00]
#IBOutlet weak var scrollView: NSScrollView!
#IBOutlet weak var clipView: NSClipView!
#IBOutlet weak var ImageView: NSImageView!
#IBOutlet var scrollerBar: NSScroller!
#IBOutlet weak var sliderControl: NSSlider!
var myPointer = CGPoint.init(x: 0.0, y: 0.0)
var fromArray:Double = 0.0
var buttonCount = 0
override func windowDidLoad() {
super.windowDidLoad()
MainWindowController.subWindowController = self // USED for midi
//Initialize midi client
var midiClient: MIDIClientRef = 0;
var inPort:MIDIPortRef = 0;
var src:MIDIEndpointRef = MIDIGetSource(0);
MIDIClientCreate("MidiTestClient" as CFString, nil, nil, &midiClient);
MIDIInputPortCreate(midiClient, "MidiTest_InPort" as CFString, MyMIDIReadProc, nil, &inPort);
MIDIPortConnectSource(inPort, src, &src);
//Set paramameters
scrollerBar.doubleValue = 0.9
let scrollViewSize = NSSize(width: 287, height: 119)
scrollView.setFrameSize(scrollViewSize)
let scrollViewColor = CGColor(red: 0.0, green: 0.0, blue: 0.9, alpha: 0.5)
scrollView.wantsLayer = false
scrollView.layer?.backgroundColor = scrollViewColor
scrollView.drawsBackground = false
let clipViewSize = NSSize(width: 1274, height: 88)
clipView.setFrameSize(clipViewSize)
let clipViewColor = CGColor(red: 0.0, green: 0.0, blue: 0.9, alpha: 0.5)
clipView.wantsLayer = false //If true image freezes but scoller moves with midi
clipView.layer?.backgroundColor = clipViewColor
clipView.drawsBackground = false
let ImageViewSize = NSSize(width: 1274, height: 87)
ImageView.setFrameSize(ImageViewSize)
/* let ImageViewColor = CGColor(red: 0.0, green: 0.9, blue: 0.0, alpha: 0.5)
ImageView.wantsLayer = false
ImageView.layer?.backgroundColor = ImageViewColor*/
}//EO Overide
func playMidi(count:Int){
//print("MIDi",count)
let fromCountIndex = count // Gets 1 to 26 from midi
//Gets double from Array 1 to 26 on Index == 0.0 to 960.0
fromArray = pointArray[fromCountIndex]
/*SCROLL BAR THINGS I HAVE TRIED*/
let scrollerValue = fromArray/960
scrollerBar.doubleValue = scrollerValue
/* scrollerBar.display()
scrollerBar.isContinuous = true */
/*CLIP VIEW SCROLL FROM ARRAY TRIGGERED BY MIDI */
myPointer = CGPoint(x:fromArray,y:0.0)
clipView.scroll(myPointer)
/*scrollView.scroll(clipView, to: myPointer)*/
/*THIS FAKES A BUTTON FIRE BUT STILL SAME PROBLEM*/
/* let mySelector = #selector(myButton(_:))
myButton(mySelector as AnyObject)*/
/* DISPLAY VIEWS I HAVE TRIED*/
// scrollView.scrollsDynamically = true
/*MOVES THE SLIDER USING MIDI*/
sliderControl.integerValue = count
}//eo playMidi
#IBAction func myButton(_ sender: AnyObject) {
fromArray = pointArray[buttonCount]
myPointer = CGPoint(x:fromArray,y:0.0)
clipView.scroll(myPointer)
buttonCount = buttonCount + 1
if (buttonCount > 26){buttonCount = 0}
print("buttonCount",buttonCount)
}
#IBAction func mySlider(_ sender: AnyObject) {
let fromSlider = sender.integerValue * 1
//Gets double from Array 1 to 26 on Index == 0.0 to 960.0 on array set to 40 when using slider moves scroll bar
fromArray = pointArray[fromSlider]
myPointer = CGPoint(x:fromArray,y:0.0)
clipView.scroll(myPointer)
print("fromSlider",fromSlider)
}
}//EnD oF thE wORld
Further to my code as above I have not answered the question but instead found a "get around". I'm really not proud of this but it seems to pose a question that maybe someone might help in answering. As the code above using the Scroll View was not working i have instead tried using CAScrollLayer.
The basic setup is as follow. I have a view inside that view I created a Scroll Layer and inside that a layer. Again as above if I use a timer to fire the scroll view everything works perfectly. And again as above if i use Midi then the scroll doesn't happen. So the work around is. The midi 1-26 reads the point value the but instead of trying to send it to the scroll it send it to a transistion variable.
I have a "Run Button" on the interface this would normally work the scrollLayerScroll. It still does this but at an incredible speed. But instead of generation its own transition variable it uses the one created buy the midi function. There is a piece of code that makes sure only a unique number is sent to the point.
This works but really deserves a prize for the worst bit of coding ever.
I think there is something in the way the timer refreshes everything that i need to extract. I hoping someone may still take pity and help
thanks again
here is the revised code Xcode 8 Swift3 OSX not iOS
import Cocoa
import CoreMIDI
class MainWindowController: NSWindowController {
static var subWindowController: MainWindowController!
var myLayer = CALayer()
var myPointer = CGPoint.init(x: 0.0, y: 0.0)
var fromArray:Double = 0.0
var buttonCount = 0
var pointArray:[Double] = [99.00,0.00,40.00,80.00,120.00,160.00,200.00,240.00,280.00,320.00,360.00,400.00,440.00,480.00,520.00,560.00,600.00,640.00,680.00,720.00,760.00,800.00,840.00,880.00,920.00,960.00,1000.00]
#IBOutlet weak var myCustomView: NSView!
var translation: CGFloat = 0.0
var newTranslation:CGFloat = 0.0
var comp6:CGFloat = 0.0
var comp6a:CGFloat = 0.0
var scrollLayer : CAScrollLayer = {
let scrollLayer = CAScrollLayer() // 8
scrollLayer.bounds = CGRect(x: 0.0, y: 0.0, width: 150.0, height: 200.0) // 9
scrollLayer.position = CGPoint(x: 300/2, y: 180/2) // 10 //not sure about this
scrollLayer.borderColor = CGColor(red: 0.0, green: 0.0, blue: 0.0, alpha: 0.8)
scrollLayer.borderWidth = 5.0 // 12
scrollLayer.scrollMode = kCAScrollHorizontally // 13
return scrollLayer
}()
var scrollTimer = Timer()
var layer = CALayer()
override func windowDidLoad() {
super.windowDidLoad()
MainWindowController.subWindowController = self // USED for midi
//Initialize midi client
var midiClient: MIDIClientRef = 0;
var inPort:MIDIPortRef = 0;
var src:MIDIEndpointRef = MIDIGetSource(0);
MIDIClientCreate("MidiTestClient" as CFString, nil, nil, &midiClient);
MIDIInputPortCreate(midiClient, "MidiTest_InPort" as CFString, MyMIDIReadProc, nil, &inPort);
MIDIPortConnectSource(inPort, src, &src);
//Set up layer
let img = NSImage(named: "smallSky.png")
let imageSize = img?.size
print (imageSize)
let layer = CALayer()
layer.bounds = CGRect(x: 0.0, y: 0.0, width: (imageSize?.width)!, height: (imageSize?.height)!) // 3
layer.position = CGPoint(x: (imageSize?.width)!/4, y: (imageSize?.height)!/2) // 4
layer.contents = img//5
myCustomView.layer?.addSublayer(scrollLayer)
scrollLayer.addSublayer(layer)
myCustomView.layer = scrollLayer
myCustomView.wantsLayer = true
}//EO Overide
#IBAction func runButton(_ sender: AnyObject) {
print("run")
//Sets of timer at very high Speed
scrollTimer = Timer.scheduledTimer(timeInterval: 0.0001, target: self, selector: #selector(MainWindowController.scrollLayerScroll as (MainWindowController) -> () -> ()), userInfo: nil, repeats: true)
}
func scrollLayerScroll(){//FIRED START FROM RUN BUTTON
//Collects data from translation and only sends to newTranslation when number is different
comp6a = translation
if (comp6a != comp6){
newTranslation = translation
print("newTranslation",newTranslation)
let newPoint = CGPoint(x: newTranslation,y:0.0)
scrollLayer.scroll(to: newPoint)
}
comp6 = comp6a
}//eo scrollLayerScroll
func playMidi(count:Int){
let fromCountIndex = count // Gets 1 to 26 from midi
fromArray = pointArray[fromCountIndex]
translation = CGFloat(fromArray)
}//eo playMidi
}//EnD oF thE wORld

Animation to scale and move UIView (swift)

I have a UIView, placed in the middle of the screen. When the user presses a button, I want it to move up near top of the screen as it shrinks to about a fifth of its size.
I have tried this:
UIView.animateWithDuration(0.7) { () -> Void in
self.main.transform = CGAffineTransformMakeScale(0.2, 0.2)
self.main.transform = CGAffineTransformMakeTranslation(0, -250)
}
But for some reason, that only scales the view. I have also tried to put this in the animateWithDuration:
self.main.frame = CGRectMake(self.view.frame.width/2 - 10, 50, 50, 50)
How can I get both animations to work?
Joe's answer above does exactly as his GIF describes but it doesn't really answer your question since it translates then scales the view (as opposed to both translating and scaling at the same time). Your issue is that you're setting the view's transform in your animation block then immediately overwritting that value with another transform. To achieve both translation and scale at the same time, you'll want something like this:
#IBAction func animateButton(_ sender: UIButton) {
let originalTransform = self.main.transform
let scaledTransform = originalTransform.scaledBy(x: 0.2, y: 0.2)
let scaledAndTranslatedTransform = scaledTransform.translatedBy(x: 0.0, y: -250.0)
UIView.animate(withDuration: 0.7, animations: {
self.main.transform = scaledAndTranslatedTransform
})
}
You can achieve basic UIView animation in several ways in Swift. Below code is just a simple approach...
class ViewController: UIViewController {
#IBOutlet weak var animationButton: UIButton!
#IBOutlet weak var myView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
// I added seperate colour to UIView when animation move from one animation block to another.So, You can get better understanding how the sequence of animation works.
self.myView.backgroundColor = .red
}
#IBAction func animateButton(_ sender: UIButton) {
UIView.animate(withDuration: 0.5, delay: 0.0, options: UIViewAnimationOptions.curveEaseIn, animations: {
//Frame Option 1:
self.myView.frame = CGRect(x: self.myView.frame.origin.x, y: 20, width: self.myView.frame.width, height: self.myView.frame.height)
//Frame Option 2:
//self.myView.center = CGPoint(x: self.view.frame.width / 2, y: self.view.frame.height / 4)
self.myView.backgroundColor = .blue
},completion: { finish in
UIView.animate(withDuration: 1, delay: 0.25,options: UIViewAnimationOptions.curveEaseOut,animations: {
self.myView.backgroundColor = .orange
self.myView.transform = CGAffineTransform(scaleX: 0.25, y: 0.25)
self.animationButton.isEnabled = false // If you want to restrict the button not to repeat animation..You can enable by setting into true
},completion: nil)})
}
}
Output:

Word by Word UILabel animation from left to right in swift

I have a uilabel with a text suppose " This is a label." I want this label to be displayed one word at a time flying from outside screen to the UILable position..
something like
label.
a label.
is a label.
This is a label.
How can i get such animation
I have found a way to do as i wished.
class ViewController: UIViewController {
#IBOutlet var sampleLabel: UILabel!
var slogan = "This is a slogan."
var xdir = 250
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
let sampleLabelFrame = sampleLabel.frame
let ypos = sampleLabelFrame.origin.y
var sloganArray = slogan.componentsSeparatedByString(" ")
sloganArray = sloganArray.reverse()
var i = 0.0
for word in sloganArray{
let label: UILabel = UILabel(frame: CGRect(x: -100, y:ypos, width: 60, height: 20))
label.text = word
view.addSubview(label)
let width = label.intrinsicContentSize().width
var labelFramewidth = label.frame
labelFramewidth.size.width = width
label.frame = labelFramewidth
self.xdir = self.xdir - Int(width)-4
UIView.animateWithDuration(0.7, delay: i, options: .CurveEaseOut, animations: {
var labelframe = label.frame
labelframe.origin.x = CGFloat(self.xdir)
label.frame = labelframe
}, completion: { finished in
})
i+=0.5
}
}
}
hope this helps others in need of something like this.
This might help you on the way to achieve what you want:
http://helpmecodeswift.com/animation/creating-animated-labels
EDIT:
Flying label code:
meme1.text = "Brace yourself!"
meme1.font = UIFont.systemFontOfSize(25)
meme1.textColor = UIColor.whiteColor() // This is all just styling the Label
meme1.sizeToFit()
meme1.center = CGPoint(x: 200, y: -50) // Starting position of the label
view.addSubview(meme1) // Important! Adding the Label to the Subview, so we can actually see it.
//Animation options. Play around with it.
UIView.animateWithDuration(0.9, delay: 0.0, usingSpringWithDamping: 0.3, initialSpringVelocity: 0.0, options: .CurveLinear, animations: {
self.meme1.center = CGPoint(x: 200, y:50 ) // Ending position of the Label
}, completion: nil)
UIView.animateWithDuration(0.6, delay: 1.1, usingSpringWithDamping: 0.3, initialSpringVelocity: 0.0, options: .CurveLinear, animations: {
self.meme1.center = CGPoint(x: 200, y:150+90 )
}, completion: nil)
I have no experience with it myself, but it seems to get your job done. You can eventually se the starting point outside the screen and let it animate over to the screen. That should give the desired effect.