Swift - AVFoundation on OS X adding sublayer issue - macos

I am trying to get a preview of my video device in a custom view.
But all I get is an empty window. I see that I have no problem accessing my camera. As soon as the app fires up i see my logitech cameras led turn on.
I assume my problem is adding the the preview layer as a sublayer.
Here is my simple code:
import Cocoa
import AVFoundation
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
#IBOutlet weak var video: NSView!
#IBOutlet weak var window: NSWindow!
let captureSession = AVCaptureSession()
var captureDevice : AVCaptureDevice?
var previewLayer : AVCaptureVideoPreviewLayer?
func applicationDidFinishLaunching(aNotification: NSNotification) {
captureSession.sessionPreset = AVCaptureSessionPresetLow
let devices = AVCaptureDevice.devices()
for device in devices {
if (device.hasMediaType(AVMediaTypeVideo)) {
captureDevice = device as? AVCaptureDevice
println(captureDevice)
}
}
if captureDevice != nil {
beginSession()
}
}
func beginSession() {
println("begin")
var err : NSError? = nil
captureSession.addInput(AVCaptureDeviceInput(device: captureDevice, error: &err))
if err != nil {
println("error: \(err?.localizedDescription)")
}
previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
previewLayer!.frame = self.video.bounds
previewLayer!.videoGravity = AVLayerVideoGravityResizeAspectFill
self.video.layer?.addSublayer(previewLayer)
captureSession.startRunning()
}
func applicationWillTerminate(aNotification: NSNotification) {
// Insert code here to tear down your application
}
}

Solved it.. At Interface Builder, I needed to add a custom view in core animation.

For my small swift-base AVFoundation grabber, this Q&A post helped me. I noticed that the following checks are important to make for the "Custom View" in the Interface builder:
Attribute Inspector > Check "Can Draw Concurrently":
View Effects Inspector > Check your custom view on the "Core Animation Layer list", (maybe even uncheck the main view controller)

In Swift 2 the error handling has changed. If you use Main.storyboard than the following approach should work:
class ViewController: NSViewController {
let captureSession = AVCaptureSession()
var captureDevice: AVCaptureDevice?
var previewLayer: AVCaptureVideoPreviewLayer?
var previewPanel: NSView!
override func viewDidLoad() {
super.viewDidLoad()
captureSession.sessionPreset = AVCaptureSessionPresetLow
let devices = AVCaptureDevice.devices()
for device in devices {
if (device.hasMediaType(AVMediaTypeVideo)) {
captureDevice = device as? AVCaptureDevice
//Swift.print("device: \(captureDevice)")
}
}
Swift.print("beginning")
do {
let deviceInput = try AVCaptureDeviceInput(device: captureDevice)
captureSession.addInput(deviceInput)
// get the CustomView as preview panel
previewPanel = self.view.subviews.first
previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
previewLayer!.frame = self.view.bounds
// add layer to preview
previewLayer!.videoGravity = AVLayerVideoGravityResizeAspectFill
previewPanel.layer?.addSublayer(previewLayer!)
captureSession.startRunning()
} catch let error as NSError {
Swift.print("Error: no valid camera input in \(error.domain)")
}
}
override var representedObject: AnyObject? {
didSet {
// Update the view, if already loaded.
}
}
Don't forget to add the CustomView.

Related

NSPopupButton has nil value

I am building an OS X desktop app that allows a user to select an item from a dropdown. I am trying to create an NSPopupButton menu like the response to this question, which is also very similar to this tutorial, but when I build and run in Xcode, I get an EXC_BAD_INSTRUCTION error and the NSPopupButton evaluates to nil in the debugger. Did I miss a step initializing the menu? I also have a text input, but it works just fine. My code:
import Cocoa
class ViewController: NSViewController {
#IBOutlet weak var textInput: NSTextField!
#IBOutlet weak var myMenu: NSPopUpButton!
// other stuff here for processing textInput
#IBAction func selectFromMyMenu(sender: NSPopUpButton) {
let selection = myMenu.titleOfSelectedItem
if selection == "Second Option" {
// do something
} else {
// do something else - first option is default
}
}
func setupMyMenu() {
let menuItems = ["First Option", "Second Option"]
myMenu.removeAllItems()
myMenu.addItemsWithTitles(menuItems)
myMenu.selectItemAtIndex(0)
}
override func viewDidLoad() {
super.viewDidLoad()
setupMyMenu()
}
override var representedObject: AnyObject? {
didSet {
// Update the view, if already loaded.
}
}
}
Try reconnecting your button from the storyboard to your ViewController code.

Creating a Scroll View Protocol in swift 2.2

I am currently developing an iOS application with login and sign up forms. To make sure that the keyboard does not cover any UITextFields I've implemented the following solution provided by Apple and discussed in this issue.
To briefly sum it up, this solution uses a UIScrollView in which the different UI elements are placed and UIKeyboardDidShowNotification and UIKeyboardDidHideNotification to move the elements up and down when the keyboard appears/disappears so that the UITextFields aren't hidden.
This works like a charm except for one thing: for all my UIViewControllers I have to repeat the same code. To tackle my problem I have tried:
to create a base UIViewController, providing an implementation for the different functions, that can be subclasses by the other UIViewControllers;
to use a protocol and a protocol extension to provide a default implementation for the different functions and make my UIViewControllers conform to it.
Both solutions didn't solve my problem. For the first solution, I wasn't able to connect the UIScrollView of my base class through the Interface Builder although it was declared.
#IBOutlet weak var scrollView: UIScrollView!
When trying to implement the second solution, the UIViewController implementing my protocol somehow did not recognise the declared methods and their implementations.
The protocol declaration:
protocol ScrollViewProtocol {
var scrollView: UIScrollView! { get set }
var activeTextField: UITextField? { get set }
func addTapGestureRecognizer()
func singleTapGestureCaptured()
func registerForKeyboardNotifications()
func deregisterForKeyboardNotifications()
func keyboardWasShown(notification: NSNotification)
func keyboardWillBeHidden(notification: NSNotification)
func setActiveTextField(textField: UITextField)
func unsetActiveTextField()
}
The protocol extension implements all functions expect for the addTapGestureRecognizer() as I would like to avoid using #objc:
extension ScrollViewProtocol where Self: UIViewController {
// The implementation for the different functions
// as described in the provided links expect for the following method
func registerFromKeyboardNotifications() {
NSNotificationCenter.defaultCenter().addObserverForName(UIKeyboardDidShowNotification, object: nil, queue: nil, usingBlock: { notification in
self.keyboardWasShown(notification)
})
NSNotificationCenter.defaultCenter().addObserverForName(UIKeyboardDidHideNotification, object: nil, queue: nil, usingBlock: { notification in
self.keyboardWillBeHidden(notification)
})
}
}
Does anyone have a good solution to my problem, knowingly how could I avoid repeating the code related to moving the UITextFields up and down when the keyboard appears/disappears? Or does anyone know why my solutions did not work?
I found a solution. I'll post it in case someone once to do the same thing.
So, I ended up deleting the UIScrollView outlet in my base class and replacing it with a simple property that I set in my inheriting classes. The code for my base class look as follow:
import UIKit
class ScrollViewController: UIViewController, UITextFieldDelegate {
// MARK: Properties
var scrollView: UIScrollView!
var activeTextField: UITextField?
// MARK: View cycle
override func viewDidLoad() {
super.viewDidLoad()
let singleTap = UITapGestureRecognizer(target: self, action: #selector(singleTapGestureCaptured))
scrollView.addGestureRecognizer(singleTap)
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
registerForKeyboardNotifications()
}
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
deregisterFromKeyboardNotifications()
}
// MARK: Gesture recognizer
func singleTapGestureCaptured(sender: AnyObject) {
view.endEditing(true)
}
// MARK: Keyboard management
func registerForKeyboardNotifications() {
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(keyboardWasShown), name: UIKeyboardWillShowNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(keyboardWillBeHidden), name: UIKeyboardWillHideNotification, object: nil)
}
func deregisterFromKeyboardNotifications() {
NSNotificationCenter.defaultCenter().removeObserver(self, name: UIKeyboardWillShowNotification, object: nil)
NSNotificationCenter.defaultCenter().removeObserver(self, name: UIKeyboardWillHideNotification, object: nil)
}
func keyboardWasShown(notification: NSNotification) {
scrollView.scrollEnabled = true
let info : NSDictionary = notification.userInfo!
let keyboardSize = (info[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.CGRectValue().size
let contentInsets : UIEdgeInsets = UIEdgeInsetsMake(0.0, 0.0, keyboardSize!.height, 0.0)
scrollView.contentInset = contentInsets
scrollView.scrollIndicatorInsets = contentInsets
var aRect : CGRect = self.view.frame
aRect.size.height -= keyboardSize!.height
if let activeFieldPresent = activeTextField {
if (!CGRectContainsPoint(aRect, activeFieldPresent.frame.origin)) {
scrollView.scrollRectToVisible(activeFieldPresent.frame, animated: true)
}
}
}
func keyboardWillBeHidden(notification: NSNotification) {
let info : NSDictionary = notification.userInfo!
let keyboardSize = (info[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.CGRectValue().size
let contentInsets : UIEdgeInsets = UIEdgeInsetsMake(0.0, 0.0, -keyboardSize!.height, 0.0)
scrollView.contentInset = contentInsets
scrollView.scrollIndicatorInsets = contentInsets
view.endEditing(true)
scrollView.scrollEnabled = false
}
// MARK: Text field management
func textFieldDidBeginEditing(textField: UITextField) {
activeTextField = textField
}
func textFieldDidEndEditing(textField: UITextField) {
activeTextField = nil
}
}
And here is the inheriting class code:
class ViewController: ScrollViewController {
#IBOutlet weak var scrollViewOutlet: UIScrollView! {
didSet {
self.scrollView = self.scrollViewOutlet
}
}
// Your view controller functions
}
I hope this will help!

Replace NSViewController under Swift2 Storyboard MAC OSX

I am new to Mac OSX and with Apple promoting the fact that the bodies of code are becoming similar decided to tell the folk I am writing code for we should be able to do a Mac OSX version. iPhone and iPad versions are all good and about to release second version so no issues there.
So I am subclassing NSWindowController to get access to the Toolbar and worked out how to remove and add items on the toolbar, but for the life of me I can not get one NSViewController (firstViewController) to dismiss and bring up the second NSViewController (secondViewController) in the same NSWindowController.
So the 2 issues are that
1. I want to be able to performSegueWithIdentifier from the first NSViewController in code and
2. bring up the second NSViewController by replacing the first NSViewController in the same NSWindowController.
If I add a button to the firstViewController and put a segue to the secondViewController then when I select the button the secondViewController comes up just fine but in a seperate window not the same NSWindowController that I want it to and the firstViewController does not get replaced but stays in the NSWindowController.
So I know the segue idea will work but its not working in code and when I do insert the segue from a button it works but into a seperate NSViewController that is not part of the NSWindowController.
I am trying to find some programming guide from Apple on the issue but no luck so far.
Here is an overview from my Storyboard:
Here is my NSWindowController subclassed and the func loginToMe2Team is trigger from the NSToolBar and its working just find as the print statements show up on the console.
import Cocoa
class me2teamWindowsController: NSWindowController {
#IBOutlet var mySignUp : NSToolbarItem!
#IBOutlet var myToolbar : NSToolbar!
let controller = ViewController()
override func windowDidLoad() {
super.windowDidLoad()
print("window loaded")
}
override func windowWillLoad() {
print("window will load")
}
#IBAction func logInToMe2Team(sender: AnyObject){
controller.LogIn() //THIS IS THE FUNC I AM TESTING WITH
}
#IBAction func signUpToMe2Team(sender: AnyObject){
controller.signUp()
}
Here is my NSViewController subclassed with the func LogIn. Its getting selected just fine but the performSegueWithIdentifier is not. And I did cut and past the Identifier to make absolutely sure it was the same.
import Cocoa
import WebKit
class ViewController: NSViewController {
#IBOutlet weak var theWebPage: WebView!
#IBOutlet weak var progressIndicator: NSProgressIndicator!
override func viewDidLoad() {
super.viewDidLoad()
let urlString = "https://thewebpage.com.au"
self.theWebPage.mainFrame.loadRequest(NSURLRequest(URL: NSURL(string: urlString)!))
}
override func viewDidAppear() {
}
func LogIn() {
print("I logged in")
self.performSegueWithIdentifier("goToTeamPage", sender: self)
//THIS IS THE BIT THATS NOT WORKING
}
func signUp() {
print("I have to sign up now")
}
override var representedObject: AnyObject? {
didSet {
}
}
func webView(sender: WebView!, didStartProvisionalLoadForFrame frame: WebFrame!)
{
self.progressIndicator.startAnimation(self)
}
func webView(sender: WebView!, didFinishLoadForFrame frame: WebFrame!)
{
self.progressIndicator.stopAnimation(self)
}
}
You need to use a custom segue class (or possibly NSTabViewController if it’s enough for your needs). Set the segue’s type to Custom, with your class name specified:
…and implement it. With no animation, it’s simple:
class ReplaceSegue: NSStoryboardSegue {
override func perform() {
if let src = self.sourceController as? NSViewController,
let dest = self.destinationController as? NSViewController,
let window = src.view.window {
// this updates the content and adjusts window size
window.contentViewController = dest
}
}
}
In my case, I was using a sheet and wanted to transition to a different sheet with a different size, so I needed to do more:
class ReplaceSheetSegue: NSStoryboardSegue {
override func perform() {
if let src = self.sourceController as? NSViewController,
let dest = self.destinationController as? NSViewController,
let window = src.view.window {
// calculate new frame:
var rect = window.frameRectForContentRect(dest.view.frame)
rect.origin.x += (src.view.frame.width - dest.view.frame.width) / 2
rect.origin.y += src.view.frame.height - dest.view.frame.height
// don’t shrink visible content, prevent minsize from intervening:
window.contentViewController = nil
// animate resizing (TODO: crossover blending):
window.setFrame(window.convertRectToScreen(rect), display: true, animate: true)
// set new controller
window.contentViewController = dest
}
}
}

Why the UIButton is so big in a simulator?

I have a UIPageViewController and a UIButton under it. Here is the screenshot of my storyboard.
When I build the app, the button is huge:
All of my constraints were set automatically. I tried to specify the height, but it doesn't help. Any ideas?
P.S. I'm using XCode 6.3.
Edit:
ViewController.swift:
import UIKit
class ViewController: UIViewController, UIPageViewControllerDataSource {
#IBOutlet weak var restartButton: UIButton!
var pageViewController: UIPageViewController!
var pageTitles: NSArray!
var pageImages: NSArray!
override func viewDidLoad() {
super.viewDidLoad()
self.pageTitles = NSArray(objects: "Page 1", "Page 2")
self.pageImages = NSArray(objects: "algorithm", "apoint")
self.pageViewController = self.storyboard?.instantiateViewControllerWithIdentifier("PageViewController") as! UIPageViewController
self.pageViewController.dataSource = self
var startVC = self.viewControllerAtIndex(0) as ContentViewController
var viewControllers = NSArray(object: startVC)
self.pageViewController.setViewControllers(viewControllers as [AnyObject], direction: .Forward, animated: true, completion: nil)
self.pageViewController.view.frame = CGRectMake(0, 0, self.view.frame.width, self.view.frame.size.height - restartButton.frame.height)
self.addChildViewController(self.pageViewController)
self.view.addSubview(self.pageViewController.view)
self.pageViewController.didMoveToParentViewController(self)
self.view.sendSubviewToBack(self.pageViewController.view)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func viewControllerAtIndex(index: Int) -> ContentViewController {
if ((self.pageTitles.count == 0) || (index >= self.pageTitles.count)) {
return ContentViewController()
}
var vc: ContentViewController = self.storyboard?.instantiateViewControllerWithIdentifier("ContentViewController") as! ContentViewController
vc.imageFile = self.pageImages[index] as! String
vc.titleText = self.pageTitles[index] as! String
vc.pageIndex = index
return vc
}
#IBAction func restartAction(sender: AnyObject) {
var startVC = self.viewControllerAtIndex(0) as ContentViewController
var viewControllers = NSArray(object: startVC)
self.pageViewController.setViewControllers(viewControllers as [AnyObject], direction: .Forward, animated: true, completion: nil)
}
func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController? {
var vc = viewController as! ContentViewController
var index = vc.pageIndex as Int
if (index == 0) || (index == NSNotFound) {
return nil
}
index--
return self.viewControllerAtIndex(index)
}
func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController? {
var vc = viewController as! ContentViewController
var index = vc.pageIndex as Int
if index == NSNotFound {
return nil
}
index++
if index == self.pageTitles.count {
return nil
}
return self.viewControllerAtIndex(index)
}
func presentationCountForPageViewController(pageViewController: UIPageViewController) -> Int {
return self.pageTitles.count
}
func presentationIndexForPageViewController(pageViewController: UIPageViewController) -> Int {
return 0
}
}
I'm going to guess that the problem is that you have a constraint from the top of the button to something else in the interface. Get rid of that constraint. The only constraints you need for a button at the bottom of the screen are its bottom and its right-or-left-or-center - its width and height are automatic.
You'll want to find your button in your Controller Scene like this, highlight your constraints and delete them.
Personally when I have constraint issues, I start from scratch by deleting all of the constraints across the board. This is just my personal approach.
Next thing I do is start with the item at the top of my storyboard view and set its constraints like this:
Notice in my add constraints window i am only selecting the top and left margins and the width and height. I do this to each of my objects starting from the top of the screen and working my way to the bottom.
Obviously you'll need to play with this feature a bit to get your desired results. Please note that what I've provided is just an example and not a fix-all.
edit:
after reading your comment I am not sure this solution will help you, I didn't realize that you made your button programmatically. I'm going to leave it up for the time being.

How to open a new window with its own ViewController from AppDelegate in Swift

I have made a statusBar application with a drop down. I would like to open a settingsWindow from that dropdown. I have made the settings window with its own ViewController.
The issue is that i can't figure out how to instantiate and show the settingsWindow that i have made. I have tried to follow every thread on the internet without any success.
My Viewcontroller:
class SettingsViewController: NSViewController {
#IBOutlet var ipAddress: NSTextField!
#IBOutlet var port: NSTextField!
#IBAction func connect(sender: AnyObject) {}
override func viewDidLoad() {
super.viewDidLoad()
}
}
My AppDelegate:
class AppDelegate: NSObject, NSApplicationDelegate {
#IBOutlet var statusMenu: NSMenu!
var statusItem: NSStatusItem?
var tcpService: TcpService = TcpService()
func applicationDidFinishLaunching(aNotification: NSNotification?) {
let bar = NSStatusBar.systemStatusBar()
statusItem = bar.statusItemWithLength(20)
statusItem!.menu = statusMenu
statusItem!.image = NSImage(byReferencingFile: NSBundle.mainBundle().pathForResource("16*16", ofType: "png"))
statusItem!.highlightMode = true
tcpService.initOutputStream("192.168.1.1", Port: 8888)
}
func applicationWillTerminate(aNotification: NSNotification?) {
// Insert code here to tear down your application
}
#IBAction func openSettings(sender: AnyObject) {
// open settings for ip and port optional port
}
}
in swift 3:
var myWindow: NSWindow? = nil
let storyboard = NSStoryboard(name: "Main",bundle: nil)
let controller: EditorViewController = storyboard.instantiateController(withIdentifier: "editorViewController") as! ViewController
myWindow = NSWindow(contentViewController: controller)
myWindow?.makeKeyAndOrderFront(self)
let vc = NSWindowController(window: myWindow)
vc.showWindow(self)
For 2022
in your normal Main storyboard, tap to add a new window controller.
tap precisely on the red "X", then the blue circle, and then enter "ExampleID" at the green entry.
in your app's ordinary main view controller, add this
variable:
var otherWindow: NSWindowController?
function:
private func otherWindow() {
let sb = NSStoryboard(name: "Main", bundle: nil)
otherWindow = sb.instantiateController(
withIdentifier: "ExampleID") as! NSWindowController
otherWindow?.showWindow(self)
}
That's it.
Call otherWindow when you want to.
Problem:
Inevitably you will want to set up the otherWindow in a certain way, example, transparent, whatever. Unfortunately this is a whole topic in itself, but you do it like this:
private func otherWindow() {
... as above ...
otherWindow?.window?.ExampleSetup()
}
and then
extension NSWindow {
func ExampleSetup() {
self.styleMask = .borderless
self.collectionBehavior = [.fullScreenPrimary]
self.level = .floating
self.isMovable = false
self.titleVisibility = .hidden
// etc etc etc ..
guard let screen = self.screen ?? NSScreen.main else {
print("what the???")
return
}
self.setFrame(screen.frame, display: true)
// consider also .visibleFrame
}
}
enum Storyboards: String {
case main = "Main"
func instantiateVC<T>(_ identifier: T.Type) -> T? {
let storyboard = NSStoryboard(name: rawValue, bundle: nil)
guard let viewcontroller = storyboard.instantiateController(withIdentifier: String(describing: identifier)) as? T else { return nil}
return viewcontroller
}
}
var ssoLoginController: IDSSOLoginViewController?
var myWindow: NSWindow? = nil
ssoLoginController = Storyboards.main.instantiateVC(IDSSOLoginViewController.self)
myWindow = NSWindow(contentViewController: ssoLoginController!)
myWindow?.makeKeyAndOrderFront(self)
let vc = NSWindowController(window: myWindow)
vc.showWindow(self)
I am not 100% that I fully understand your problem, but assuming that you are using a storyboard (you should if you are starting fresh), adding few lines to your applicationDidFinishLaunching method will help:
var myWindow: NSWindow? = nil
let storyboard = NSStoryboard(name: "Main",bundle: nil)
let controller: SettingsViewController = storyboard?.instantiateControllerWithIdentifier("SettingsViewController") as SettingsViewController
myWindow = controller.window
myWindow?.makeKeyAndOrderFront(self)
Do not forget to set the Storyboard ID in IB (in the example above to SettingsViewController)!

Resources