UINavigationController-Alike for Desktop-Cocoa? - cocoa

I'm currently developing an application with an user interface much like Twitter for Mac (Pushing in/out of views like on iOS).
Has anyone implemented a UIViewController for desktop Cocoa? This would save me many hours of work.

There isn't one in standard AppKit at this time. You'll have to write your own.
This may help if you decide to go down that path: http://parsekit.com/umekit/
UMEKit is a little framework for Cocoa that implements some equivalents to UIKit classes and UI components.

I spent ages looking for this and started to write my own, then found https://github.com/bfolder/BFNavigationController
For some reason Google doesn't know about it.

Here how we did NavigationController, without navigation bar. Implementing navigation bar with transitions between navigation items similar to how transition between views made in example below.
import AppKit
public class NavigationController: NSViewController {
public private (set) var viewControllers: [NSViewController] = []
open override func loadView() {
view = NSView()
view.wantsLayer = true
}
public init(rootViewController: NSViewController) {
super.init(nibName: nil, bundle: nil)
pushViewController(rootViewController, animated: false)
}
public required init?(coder: NSCoder) {
fatalError()
}
}
extension NavigationController {
public var topViewController: NSViewController? {
return viewControllers.last
}
public func pushViewControllerAnimated(_ viewController: NSViewController) {
pushViewController(viewController, animated: true)
}
public func pushViewController(_ viewController: NSViewController, animated: Bool) {
viewController.navigationController = self
viewController.view.wantsLayer = true
if animated, let oldVC = topViewController {
embedChildViewController(viewController)
let endFrame = oldVC.view.frame
let startFrame = endFrame.offsetBy(dx: endFrame.width, dy: 0)
viewController.view.frame = startFrame
viewController.view.alphaValue = 0.85
viewControllers.append(viewController)
NSAnimationContext.runAnimationGroup({ context in
context.duration = 0.2
context.allowsImplicitAnimation = true
context.timingFunction = .easeOut
viewController.view.animator().frame = endFrame
viewController.view.animator().alphaValue = 1
oldVC.view.animator().alphaValue = 0.25
}) {
oldVC.view.alphaValue = 1
oldVC.view.removeFromSuperview()
}
} else {
embedChildViewController(viewController)
viewControllers.append(viewController)
}
}
#discardableResult
public func popViewControllerAnimated() -> NSViewController? {
return popViewController(animated: true)
}
#discardableResult
public func popViewController(animated: Bool) -> NSViewController? {
guard let oldVC = viewControllers.popLast() else {
return nil
}
if animated, let newVC = topViewController {
let endFrame = oldVC.view.frame.offsetBy(dx: oldVC.view.frame.width, dy: 0)
view.addSubview(newVC.view, positioned: .below, relativeTo: oldVC.view)
NSAnimationContext.runAnimationGroup({ context in
context.duration = 0.23
context.allowsImplicitAnimation = true
context.timingFunction = .easeIn
oldVC.view.animator().frame = endFrame
oldVC.view.animator().alphaValue = 0.85
}) {
self.unembedChildViewController(oldVC)
}
} else {
unembedChildViewController(oldVC)
}
return oldVC
}
}
Reusable extensions:
extension NSViewController {
private struct OBJCAssociationKey {
static var navigationController = "com.mc.navigationController"
}
public var navigationController: NavigationController? {
get {
return ObjCAssociation.value(from: self, forKey: &OBJCAssociationKey.navigationController)
} set {
ObjCAssociation.setAssign(value: newValue, to: self, forKey: &OBJCAssociationKey.navigationController)
}
}
}
extension NSViewController {
public func embedChildViewController(_ vc: NSViewController, container: NSView? = nil) {
addChildViewController(vc)
vc.view.frame = CGRect(x: 0, y: 0, width: view.frame.width, height: view.frame.height)
vc.view.autoresizingMask = [.height, .width]
(container ?? view).addSubview(vc.view)
}
public func unembedChildViewController(_ vc: NSViewController) {
vc.view.removeFromSuperview()
vc.removeFromParentViewController()
}
}
struct ObjCAssociation {
static func value<T>(from object: AnyObject, forKey key: UnsafeRawPointer) -> T? {
return objc_getAssociatedObject(object, key) as? T
}
static func setAssign<T>(value: T?, to object: Any, forKey key: UnsafeRawPointer) {
objc_setAssociatedObject(object, key, value, .OBJC_ASSOCIATION_ASSIGN)
}
static func setRetainNonAtomic<T>(value: T?, to object: Any, forKey key: UnsafeRawPointer) {
objc_setAssociatedObject(object, key, value, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
static func setCopyNonAtomic<T>(value: T?, to object: Any, forKey key: UnsafeRawPointer) {
objc_setAssociatedObject(object, key, value, .OBJC_ASSOCIATION_COPY_NONATOMIC)
}
static func setRetain<T>(value: T?, to object: Any, forKey key: UnsafeRawPointer) {
objc_setAssociatedObject(object, key, value, .OBJC_ASSOCIATION_RETAIN)
}
static func setCopy<T>(value: T?, to object: Any, forKey key: UnsafeRawPointer) {
objc_setAssociatedObject(object, key, value, .OBJC_ASSOCIATION_COPY)
}
}

Related

Search for places/ locations using MapKit and Search Bar (SwiftUI, Xcode 12.4)

I have a question about how one can connect a Search Bar with MapKit, so that it is able to search for places/ locations (not using StoryBoard). I have already written the code for the Search Bar and for the MapView in separate files, but even after trying literally every code and tutorial on the internet, I couldn't find a way to connect the Search Bar to search for locations. Below one can see respectively the used SearchBar.swift file, the MapViewController.swift and a snippet of the ContentView.swift.
SearchBar.swift
import UIKit
import Foundation
import SwiftUI
import MapKit
struct SearchBar: UIViewRepresentable {
// Binding: A property wrapper type that can read and write a value owned by a source of truth.
#Binding var text: String
// NSObject: The root class of most Objective-C class hierarchies, from which subclasses inherit a basic interface to the runtime system and the ability to behave as Objective-C objects.
// UISearchBarDelegate: A collection of optional methods that you implement to make a search bar control functional.
class Coordinator: NSObject, UISearchBarDelegate {
#Binding var text: String
let Map = MapViewController()
init(text: Binding<String>) {
_text = text
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
text = searchText
}
func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) {
}
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
text = ""
searchBar.showsCancelButton = true
searchBar.endEditing(true)
searchBar.resignFirstResponder()
}
}
func makeCoordinator() -> SearchBar.Coordinator {
return Coordinator(text: $text)
}
func makeUIView(context: UIViewRepresentableContext<SearchBar>) -> UISearchBar {
let searchBar = UISearchBar(frame: .zero)
searchBar.delegate = context.coordinator
searchBar.showsCancelButton = true
searchBar.searchBarStyle = .minimal
//searchBar.backgroundColor = .opaqueSeparator
searchBar.showsCancelButton = true
return searchBar
}
func updateUIView(_ uiView: UIViewType, context: Context) {
uiView.text = text
}
}
MapViewController.swift
class MapViewController: UIViewController, CLLocationManagerDelegate {
let mapView = MKMapView()
let locationManager = CLLocationManager()
#Published var permissionDenied = false
override func viewDidLoad() {
super.viewDidLoad()
setupMapView()
checkLocationServices()
}
func setupMapView() {
view.addSubview(mapView)
mapView.translatesAutoresizingMaskIntoConstraints = false
mapView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
mapView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
mapView.rightAnchor.constraint(equalTo: view.safeAreaLayoutGuide.rightAnchor).isActive = true
mapView.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor).isActive = true
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let span = MKCoordinateSpan(latitudeDelta: 0.005, longitudeDelta: 0.005)
guard let location = locations.last else { return }
let region = MKCoordinateRegion(center: location.coordinate, span: span)
mapView.setRegion(region, animated: true)
let categories:[MKPointOfInterestCategory] = [.cafe, .restaurant]
let filters = MKPointOfInterestFilter(including: categories)
mapView.pointOfInterestFilter = .some(filters)
// Enables the scrolling around the user location without hopping back
locationManager.stopUpdatingLocation()
}
func checkLocalAuthorization() {
switch CLLocationManager.authorizationStatus() {
case .authorizedWhenInUse:
mapView.showsUserLocation = true
followUserLocation()
locationManager.startUpdatingLocation()
break
case .denied:
permissionDenied.toggle()
break
case .notDetermined:
locationManager.requestWhenInUseAuthorization()
case .restricted:
// Show alert
break
case .authorizedAlways:
break
#unknown default:
fatalError()
}
}
func checkLocationServices() {
if CLLocationManager.locationServicesEnabled() {
setupLocationManager()
checkLocalAuthorization()
} else {
// user did not turn it on
}
}
func followUserLocation() {
if let location = locationManager.location?.coordinate {
let region = MKCoordinateRegion.init(center: location, latitudinalMeters: 4000, longitudinalMeters: 4000)
mapView.setRegion(region, animated: true)
}
}
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
checkLocalAuthorization()
}
func setupLocationManager() {
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print(error.localizedDescription)
}
}
The methods are then called in the ContentView.swift, using these methods:
struct MapViewRepresentable: UIViewControllerRepresentable {
func makeUIViewController(context: Context) -> some UIViewController {
return MapViewController()
}
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {
}
}
struct ContentView: View {
#State private var searchText : String = ""
var body: some View {
ZStack(alignment: .top) {
MapViewRepresentable()
.edgesIgnoringSafeArea(.all)
.onTapGesture {
self.endTextEditing()
}
SearchBar(text: $searchText)
}
}
}
Is it possible to connect both like I explained, or is there another method you advice? I really hope you guys can help me! Thanks in advance :)

In SwiftUI, how can I add a video on loop as a fullscreen background image?

I have a video thats around 10 seconds long that I'd like to play on a loop as a fullscreen background image in one of my SwiftUI Views. How can I implement this?
First idea was working with Swift's import AVFoundation, but not sure if this is the right path.
You can use the AV family of frameworks and UIViewRepresentable to do this:
import SwiftUI
import AVKit
struct PlayerView: UIViewRepresentable {
func updateUIView(_ uiView: UIView, context: UIViewRepresentableContext<PlayerView>) {
}
func makeUIView(context: Context) -> UIView {
return PlayerUIView(frame: .zero)
}
}
In order for the video to loop I have added an observer and set the actionAtItemEnd to .none to support looping.
When the video reaches the end it will execute the playerItemDidReachEnd(...) method and seek to the beginning of the video and keep looping.
The example points to a remote video URL. If you want to point to a file within your application you can use Bundle.main.url to do so instead:
if let fileURL = Bundle.main.url(forResource: "IMG_2770", withExtension: "MOV") {
let player = AVPlayer(url: fileURL)
// ...
}
class PlayerUIView: UIView {
private let playerLayer = AVPlayerLayer()
override init(frame: CGRect) {
super.init(frame: frame)
let url = URL(string: "https://bitdash-a.akamaihd.net/content/sintel/hls/playlist.m3u8")!
let player = AVPlayer(url: url)
player.actionAtItemEnd = .none
player.play()
playerLayer.player = player
playerLayer.videoGravity = .resizeAspectFill
NotificationCenter.default.addObserver(self,
selector: #selector(playerItemDidReachEnd(notification:)),
name: .AVPlayerItemDidPlayToEndTime,
object: player.currentItem)
layer.addSublayer(playerLayer)
}
#objc func playerItemDidReachEnd(notification: Notification) {
if let playerItem = notification.object as? AVPlayerItem {
playerItem.seek(to: .zero, completionHandler: nil)
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
playerLayer.frame = bounds
}
}
struct ContentView: View {
var body: some View {
NavigationView {
ZStack {
PlayerView()
.edgesIgnoringSafeArea(.all)
}
}
}
}
SwiftUI
As someone completely new to swift and for anyone who doesn't want to spend hours debugging this like I did. My use case was trying to create a login screen with a video playing in the background. I was struggling with the looping not working and then with the video stopping after a few seconds and starting again after the duration. This works for me.
Add a new view:
import SwiftUI
import AVKit
import AVFoundation
struct WelcomeVideo: View {
var body: some View {
WelcomeVideoController()
}
}
struct WelcomeVideo_Previews: PreviewProvider {
static var previews: some View {
WelcomeVideo()
}
}
final class WelcomeVideoController : UIViewControllerRepresentable {
var playerLooper: AVPlayerLooper?
func makeUIViewController(context: UIViewControllerRepresentableContext<WelcomeVideoController>) ->
AVPlayerViewController {
let controller = AVPlayerViewController()
controller.showsPlaybackControls = false
guard let path = Bundle.main.path(forResource: "welcome", ofType:"mp4") else {
debugPrint("welcome.mp4 not found")
return controller
}
let asset = AVAsset(url: URL(fileURLWithPath: path))
let playerItem = AVPlayerItem(asset: asset)
let queuePlayer = AVQueuePlayer()
// OR let queuePlayer = AVQueuePlayer(items: [playerItem]) to pass in items
playerLooper = AVPlayerLooper(player: queuePlayer, templateItem: playerItem)
queuePlayer.play()
controller.player = queuePlayer
return controller
}
func updateUIViewController(_ uiViewController: AVPlayerViewController, context: UIViewControllerRepresentableContext<WelcomeVideoController>) {
}
}
Then attach it to a view background:
.background(WelcomeVideo())
NOTE:
Make sure your video is imported to your project
Update the name of the video to what you need or refactor slightly to pass it in
Cheers!
This is what worked for me:
source
var body: some View {
ZStack{
HStack{
Spacer()
.frame(width: 50)
AmbienceVid()
}
.edgesIgnoringSafeArea(.all)
}
}
struct AmbienceVid: UIViewRepresentable {
func updateUIView(_ uiView: UIView, context: UIViewRepresentableContext<AmbienceVid>) {
}
func makeUIView(context: Context) -> UIView {
return PlayerUIView(frame: .zero)
}
}
class PlayerUIView: UIView {
private var playerLooper: AVPlayerLooper?
private var playerLayer = AVPlayerLayer()
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override init(frame: CGRect) {
super.init(frame: frame)
// Load the resource
let fileUrl = Bundle.main.url(forResource: "ambiencevid", withExtension: "mp4")!
let asset = AVAsset(url: fileUrl)
let item = AVPlayerItem(asset: asset)
// Setup the player
let player = AVQueuePlayer()
playerLayer.player = player
playerLayer.videoGravity = .resizeAspectFill
layer.addSublayer(playerLayer)
// Create a new player looper with the queue player and template item
playerLooper = AVPlayerLooper(player: player, templateItem: item)
// Start the movie
player.play()
}
override func layoutSubviews() {
super.layoutSubviews()
playerLayer.frame = bounds
}
}
A looping, no-controls macOS implementation if people were searching for it.
import SwiftUI
import AVKit
struct NSVideoPlayer: NSViewRepresentable {
var videoURL: URL
func makeNSView(context: Context) -> AVPlayerView {
let item = AVPlayerItem(url: videoURL)
let queue = AVQueuePlayer(playerItem: item)
context.coordinator.looper = AVPlayerLooper(player: queue, templateItem: item)
let view = AVPlayerView()
view.player = queue
view.controlsStyle = .none
view.player?.playImmediately(atRate: 1)
return view
}
func updateNSView(_ nsView: AVPlayerView, context: Context) {}
func makeCoordinator() -> Coordinator {
Coordinator()
}
class Coordinator {
var looper: AVPlayerLooper? = nil
}
}
Tested in Swift 5 and SwiftUI 3
Viewmodel class functions
var avPlayer = AVPlayer()
func previewPlayer() -> AVPlayer {
self.avPlayer = AVPlayer(url: vedioData.preWithWithDecoURL!)
return self.avPlayer
}
func loopCurrentVedio() {
NotificationCenter.default.addObserver(forName: .AVPlayerItemDidPlayToEndTime, object: nil, queue: .main) { _ in
self.avPlayer.seek(to: .zero)
self.avPlayer.play()
}
}
In your SwiftUI View class
VideoPlayer(player: previewPlayer())
.frame(width: 300, height: 532, alignment: .center)
.cornerRadius(20)
.onAppear {
loopCurrentVedio()
}
This is the simplest solution I found

Can't see banner for iAds

Does any body else have the problem were they can't see the banner for iAds but when you first run the app a big blue screen shows up and says your now connected to iAds. I'm running my app an my iPhone and my iAD developer app testing fill rate is set to 100%
Code:
import UIKit
import StoreKit
import SpriteKit
import GameKit
import iAd
extension SKNode {
class func unarchiveFromFile(file : String) -> SKNode? {
if let path = NSBundle.mainBundle().pathForResource(file, ofType: "sks") {
var sceneData = NSData(contentsOfFile: path, options: .DataReadingMappedIfSafe, error: nil)!
var archiver = NSKeyedUnarchiver(forReadingWithData: sceneData)
archiver.setClass(self.classForKeyedUnarchiver(), forClassName: "SKScene")
let scene = archiver.decodeObjectForKey(NSKeyedArchiveRootObjectKey) as! GameScene
archiver.finishDecoding()
return scene
} else {
return nil
}
}
}
class GameViewController: UIViewController, ADInterstitialAdDelegate {
var interstitialAd:ADInterstitialAd!
var interstitialAdView: UIView = UIView()
override func viewDidLoad() {
super.viewDidLoad()
loadInterstitialAd()
ADBannerView()
func gameCenterViewControllerDidFinish(gameCenterViewController: GKGameCenterViewController!)
{
gameCenterViewController.dismissViewControllerAnimated(true, completion: nil)
}
var localPlayer = GKLocalPlayer()
localPlayer.authenticateHandler = {(viewController, error) -> Void in
if (viewController != nil) {
let vc: UIViewController = self.view!.window!.rootViewController!
vc.presentViewController(viewController, animated: true, completion: nil)
}
else {
println((GKLocalPlayer.localPlayer().authenticated))
}
}
if let scene = GameScene.unarchiveFromFile("GameScene") as? GameScene {
// Configure the view.
let skView = self.view as! SKView
skView.showsFPS = false
skView.showsNodeCount = false
/* Sprite Kit applies additional optimizations to improve rendering performance */
skView.ignoresSiblingOrder = true
/* Set the scale mode to scale to fit the window */
scene.scaleMode = .AspectFill
skView.presentScene(scene)
}
}
override func shouldAutorotate() -> Bool {
return true
}
override func supportedInterfaceOrientations() -> Int {
if UIDevice.currentDevice().userInterfaceIdiom == .Phone {
return Int(UIInterfaceOrientationMask.AllButUpsideDown.rawValue)
} else {
return Int(UIInterfaceOrientationMask.All.rawValue)
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Release any cached data, images, etc that aren't in use.
}
override func prefersStatusBarHidden() -> Bool {
return true
}
func loadInterstitialAd() {
interstitialAd = ADInterstitialAd()
interstitialAd.delegate = self
}
func interstitialAdWillLoad(interstitialAd: ADInterstitialAd!) {
}
func interstitialAdDidLoad(interstitialAd: ADInterstitialAd!) {
interstitialAdView = UIView()
interstitialAdView.frame = self.view.bounds
view.addSubview(interstitialAdView)
interstitialAd.presentInView(interstitialAdView)
UIViewController.prepareInterstitialAds()
}
func interstitialAdActionDidFinish(interstitialAd: ADInterstitialAd!) {
interstitialAdView.removeFromSuperview()
}
func interstitialAdActionShouldBegin(interstitialAd: ADInterstitialAd!, willLeaveApplication willLeave: Bool) -> Bool {
return true
}
func interstitialAd(interstitialAd: ADInterstitialAd!, didFailWithError error: NSError!) {
}
func interstitialAdDidUnload(interstitialAd: ADInterstitialAd!) {
interstitialAdView.removeFromSuperview()
}
}

Custom NSView Drag and Drop images

I'm working on an OS X App and, I can't seem to get Drag and Drop to work. I've Googled a lot, but most posts about this subject are at least a few years old and none of them tells me the missing link I have in my thoughts.
Anyway, here is what I'm trying to do. I have an image somewhere on my desktop and I want the ability to drag and drop that into my Custom NSView. The custom view is a child object of a custom NSView named CircularImageView and is layer backed and only shows a circular shaped image on the screen.
Here's the code:
import Cocoa
import MCTools
#objc public protocol DragAndDropCircularImageViewDelegate {
func imageDumped(sender: AnyObject!)
}
#IBDesignable #objc public class DragAndDropCircularImageView: CircularImageView {
// This class provides the Drag And Drop Feature to the CircularImageView Class.
// MARK: New in this class
var highlight: Bool = false
public var delegate: DragAndDropCircularImageViewDelegate?
private func registerForDraggedImages() {
self.registerForDraggedTypes(NSImage.imageTypes())
}
// MARK: CircularImageView Stuff
public override var image: NSImage? {
didSet {
if let newImage = image {
delegate?.imageDumped(self)
}
}
}
public required init?(coder: NSCoder) {
super.init(coder: coder)
self.registerForDraggedImages()
}
public override init(frame frameRect: NSRect) {
super.init(frame: frameRect)
self.registerForDraggedImages()
}
public override func updateLayer() {
super.updateLayer()
if highlight == true {
}
}
// MARK: NS Dragging Destination Protocol
public override func draggingEntered(sender: NSDraggingInfo) -> NSDragOperation {
// When a drag enters our drop zone.
if NSImage.canInitWithPasteboard(sender.draggingPasteboard()) {
if ((sender.draggingSourceOperationMask().rawValue & NSDragOperation.Copy.rawValue) > 0) {
highlight = true
self.needsLayout = true
sender.enumerateDraggingItemsWithOptions(.Concurrent, forView: self, classes: [NSPasteboardItem.self], searchOptions: [NSPasteboardURLReadingContentsConformToTypesKey: self], usingBlock: { (draggingItem, idx, stop) -> Void in
return
})
}
return NSDragOperation.Copy
}
return NSDragOperation.None
}
public override func draggingExited(sender: NSDraggingInfo?) {
// When drag exits our drop zone remove highlight of the drop zone.
println("\(self)draggingExited")
highlight = false
self.needsLayout = true
}
public override func prepareForDragOperation(sender: NSDraggingInfo) -> Bool {
// Update view for hovering drop.
println("\(self)prepareForDragOperation")
highlight = false
self.needsLayout = true
// Can we accept the drop?
return NSImage.canInitWithPasteboard(sender.draggingPasteboard())
}
public override func performDragOperation(sender: NSDraggingInfo) -> Bool {
// Handle the drop data.
println("\(self)performDragOperation \(sender)")
if NSImage.canInitWithPasteboard(sender.draggingPasteboard()) {
self.image = NSImage(pasteboard: sender.draggingPasteboard())
}
return true
}
// MARK: Interface Builder Stuff
}
I have seen some posts that I should be using:
self.registerForDraggedTypes([NSFilenamesPboardType])
instead of:
self.registerForDraggedTypes(NSImage.imageTypes())
But this doesn't seem to work in my case, when I'm using NSFileNamesPboardType I get the following debug message even before any of the NSDraggingDestination protocol messages have been called:
2015-05-07 11:07:19.583 CircularImageViewTest[44809:14389647] -[CircularView.DragAndDropCircularImageView copyWithZone:]: unrecognized selector sent to instance 0x608000166d80
(lldb) p 0x608000166d80
(Int) $R0 = 106102873550208
I don't understand how this works. Somewhere the frameworks try to copyWithZone on an integer? Can anyone explain this to me?
Any help would be appreciated. Thanks in advance.
Ok, the code below works. It was all caused by sender.enumerateDraggingItemsWithOptions in draggingEntered. Something goes wrong in the Apple frameworks when it is called.
import Cocoa
import MCTools
#objc public protocol DragAndDropCircularImageViewDelegate {
func imageDumped(sender: AnyObject!)
}
#IBDesignable #objc public class DragAndDropCircularImageView: CircularImageView {
// This class provides the Drag And Drop Feature to the CircularImageView Class.
// MARK: New in this class
var highlight: Bool = false
public weak var delegate: DragAndDropCircularImageViewDelegate?
private func registerForDraggedImages() {
// self.registerForDraggedTypes(NSImage.imageTypes())
self.registerForDraggedTypes([NSFilenamesPboardType])
}
// MARK: CircularImageView Stuff
public override var image: NSImage? {
didSet {
if let newImage = image {
delegate?.imageDumped(self)
}
}
}
public required init?(coder: NSCoder) {
super.init(coder: coder)
self.registerForDraggedImages()
}
public override init(frame frameRect: NSRect) {
super.init(frame: frameRect)
self.registerForDraggedImages()
}
public override func updateLayer() {
super.updateLayer()
if highlight == true {
}
}
// MARK: NS Dragging Destination Protocol
public override func draggingEntered(sender: NSDraggingInfo) -> NSDragOperation {
// When a drag enters our drop zone.
if NSImage.canInitWithPasteboard(sender.draggingPasteboard()) {
if ((sender.draggingSourceOperationMask().rawValue & NSDragOperation.Copy.rawValue) > 0) {
highlight = true
self.needsLayout = true
}
return NSDragOperation.Copy
}
return NSDragOperation.None
}
public override func draggingExited(sender: NSDraggingInfo?) {
// When drag exits our drop zone remove highlight of the drop zone.
println("\(self)draggingExited")
highlight = false
self.needsLayout = true
}
public override func prepareForDragOperation(sender: NSDraggingInfo) -> Bool {
// Update view for hovering drop.
println("\(self)prepareForDragOperation")
highlight = false
self.needsLayout = true
// Can we accept the drop?
return NSImage.canInitWithPasteboard(sender.draggingPasteboard())
}
public override func performDragOperation(sender: NSDraggingInfo) -> Bool {
// Handle the drop data.
println("\(self)performDragOperation \(sender)")
if NSImage.canInitWithPasteboard(sender.draggingPasteboard()) {
self.image = NSImage(pasteboard: sender.draggingPasteboard())
self.delegate!.imageDumped(self)
}
return true
}
// MARK: Interface Builder Stuff
}

Creating a Cocoa application without NIB files

Yes, I know this goes against the whole MVC principle!
However, I'm just trying to whip up a pretty trivial application - and I've pretty much implemented it. However, I have a problem...
I create an empty project, copy all the frameworks over and set the build settings - and I get errors about the executable, or lack of executable. The build settings all appear fine, but it tells me there is no executable - it will build + run fine. However it doesn't run. There is no error either - it just appears to run very fast and cleanly! Unless I try and run GDB which politely tells me I need to give it a file first..
Running…
No executable file specified.
Use the "file" or "exec-file" command.
So I created a Cocoa application, removed all the stuff I didn't need (that is, the MainMenu.xib file..), and now I can compile my code perfectly. However it dies complaining that it's
"Unable to load nib file: MainMenu, exiting"
I have gone through the Project Symbols and see that the code actually relies upon the NIB file heavily, even if you don't touch it code-wise. (MVC again I guess..)
Is there a simple way to compile just what you code, no added NIB files, just the code you write and the frameworks you add? I assume it would be a blank project, but my experience tells me otherwise?!
This is the method I use in my applications. Sorry for the formatting, I hope you can make it out. I don’t know how to turn off the auto-formatting here.
Of course there will be no functioning main menu out of this example, that’s far too much code for me to write on a post like this :P - Sorry, out do some research on that ;)
This should get you started:
AppDelegate.h
#interface MyApplicationDelegate : NSObject <NSApplicationDelegate, NSWindowDelegate> {
NSWindow * window;
}
#end
AppDelegate.m
#implementation MyApplicationDelegate : NSObject
- (id)init {
if (self = [super init]) {
// allocate and initialize window and stuff here ..
}
return self;
}
- (void)applicationWillFinishLaunching:(NSNotification *)notification {
[window makeKeyAndOrderFront:self];
}
- (void)dealloc {
[window release];
[super dealloc];
}
#end
main.m
#import "AppDelegate.h"
int main(int argc, char * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
NSApplication * application = [NSApplication sharedApplication];
MyApplicationDelegate * appDelegate = [[[[MyApplicationDelegate]alloc] init] autorelease];
[application setDelegate:appDelegate];
[application run];
[pool drain];
return EXIT_SUCCESS;
}
int main() {
[NSAutoreleasePool new];
[NSApplication sharedApplication];
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
id menubar = [[NSMenu new] autorelease];
id appMenuItem = [[NSMenuItem new] autorelease];
[menubar addItem:appMenuItem];
[NSApp setMainMenu:menubar];
id appMenu = [[NSMenu new] autorelease];
id appName = [[NSProcessInfo processInfo] processName];
id quitTitle = [#"Quit " stringByAppendingString:appName];
id quitMenuItem = [[[NSMenuItem alloc] initWithTitle:quitTitle
action:#selector(terminate:) keyEquivalent:#"q"] autorelease];
[appMenu addItem:quitMenuItem];
[appMenuItem setSubmenu:appMenu];
id window = [[[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, 200, 200)
styleMask:NSTitledWindowMask backing:NSBackingStoreBuffered defer:NO]
autorelease];
[window cascadeTopLeftFromPoint:NSMakePoint(20,20)];
[window setTitle:appName];
[window makeKeyAndOrderFront:nil];
[NSApp activateIgnoringOtherApps:YES];
[NSApp run];
return 0;
}
Though this is a few years old question...
Here's minimal code snippet to bootstrap a Cocoa application in Swift.
import AppKit
final class ExampleApplicationController: NSObject, NSApplicationDelegate {
let window1 = NSWindow()
func applicationDidFinishLaunching(aNotification: NSNotification) {
window1.setFrame(CGRect(x: 0, y: 0, width: 800, height: 500), display: true)
window1.makeKeyAndOrderFront(self)
}
func applicationWillTerminate(aNotification: NSNotification) {
}
}
autoreleasepool { () -> () in
let app1 = NSApplication.sharedApplication()
let con1 = ExampleApplicationController()
app1.delegate = con1
app1.run()
}
Also, I am maintaining a bunch of programmatic examples for Cocoa including bootstrapping, window, menu creations.
CocoaProgrammaticHowtoCollection
See subprojects for desired language.
Swift examples
Objective-C Examples
Of course you can write just code and not use Interface Builder.
Have you checked your Info.plist? By default there is an entry there for MainMenu.xib and it may be that reference it's complaining about.
Swift 4 version with NSToolbar and NSMenu (with event handlers instead of delegates):
File main.swift:
autoreleasepool {
// Even if we loading application manually we need to setup `Info.plist` key:
// <key>NSPrincipalClass</key>
// <string>NSApplication</string>
// Otherwise Application will be loaded in `low resolution` mode.
let app = Application.shared
app.setActivationPolicy(.regular)
app.run()
}
File: Application.swift
class Application: NSApplication {
private lazy var mainWindowController = MainWindowController()
private lazy var mainAppMenu = MainMenu()
override init() {
super.init()
setupUI()
setupHandlers()
}
required init?(coder: NSCoder) {
super.init(coder: coder) // This will never called.
}
}
extension Application: NSApplicationDelegate {
func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
return true
}
func applicationDidFinishLaunching(_ aNotification: Notification) {
mainWindowController.showWindow(nil)
}
}
extension Application {
private func setupUI() {
mainMenu = mainAppMenu
}
private func setupHandlers() {
delegate = self
mainAppMenu.eventHandler = { [weak self] in
switch $0 {
case .quit:
self?.terminate(nil)
}
}
}
}
File MainWindowController.swift
class MainWindowController: NSWindowController {
private (set) lazy var viewController = MainViewController()
private (set) lazy var mainToolbar = MainToolbar(identifier: NSToolbar.Identifier("ua.com.wavelabs.Decoder:mainToolbar"))
init() {
let window = NSWindow(contentRect: CGRect(x: 400, y: 200, width: 800, height: 600),
styleMask: [.titled, .closable, .resizable, .miniaturizable],
backing: .buffered,
defer: true)
super.init(window: window)
let frameSize = window.contentRect(forFrameRect: window.frame).size
viewController.view.setFrameSize(frameSize)
window.contentViewController = viewController
window.titleVisibility = .hidden
window.toolbar = mainToolbar
setupHandlers()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
}
extension MainWindowController {
private func setupHandlers() {
mainToolbar.eventHandler = {
print($0)
}
}
}
File MainViewController.swift
class MainViewController: NSViewController {
init() {
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func loadView() {
view = NSView()
view.wantsLayer = true
view.layer?.backgroundColor = NSColor.magenta.cgColor
}
}
File MainToolbar.swift
class MainToolbar: NSToolbar {
enum Event: Int {
case toggleSidePanel
}
let toolbarDelegate = GenericDelegate()
var eventHandler: ((MainToolbar.Event) -> Void)?
override init(identifier: NSToolbar.Identifier) {
super.init(identifier: identifier)
setupUI()
setupHandlers()
}
}
extension MainToolbar {
private func setupUI() {
allowsUserCustomization = true
autosavesConfiguration = true
displayMode = .iconOnly
toolbarDelegate.allowedItemIdentifiers = [.space, .flexibleSpace]
toolbarDelegate.selectableItemIdentifiers = [.space, .flexibleSpace]
toolbarDelegate.defaultItemIdentifiers = Event.toolbarIDs + [.flexibleSpace]
}
private func setupHandlers() {
delegate = toolbarDelegate
toolbarDelegate.makeItemCallback = { [unowned self] id, _ in
guard let event = Event(id: id) else {
return nil
}
return self.makeToolbarItem(event: event)
}
}
private func makeToolbarItem(event: Event) -> NSToolbarItem {
let item = NSToolbarItem(itemIdentifier: event.itemIdentifier)
item.setHandler { [weak self] in
guard let event = Event(id: event.itemIdentifier) else {
return
}
self?.eventHandler?(event)
}
item.label = event.label
item.paletteLabel = event.paletteLabel
if event.image != nil {
item.image = event.image
} else if event.view != nil {
item.view = event.view
}
return item
}
}
extension MainToolbar.Event {
init?(id: NSToolbarItem.Identifier) {
guard let event = (MainToolbar.Event.allValues.filter { $0.itemIdentifier == id }).first else {
return nil
}
self = event
}
static var allValues: [MainToolbar.Event] {
return [toggleSidePanel]
}
static var toolbarIDs: [NSToolbarItem.Identifier] {
return [toggleSidePanel].map { $0.itemIdentifier }
}
var itemIdentifier: NSToolbarItem.Identifier {
switch self {
case .toggleSidePanel: return NSToolbarItem.Identifier("ua.com.wavalabs.toolbar.toggleSidePanel")
}
}
var label: String {
switch self {
case .toggleSidePanel: return "Toggle Side Panel"
}
}
var view: NSView? {
return nil
}
var image: NSImage? {
switch self {
case .toggleSidePanel: return NSImage(named: NSImage.Name.folder)
}
}
var paletteLabel: String {
return label
}
}
File MainMenu.swift
class MainMenu: NSMenu {
enum Event {
case quit
}
var eventHandler: ((Event) -> Void)?
private lazy var applicationName = ProcessInfo.processInfo.processName
init() {
super.init(title: "")
setupUI()
}
required init(coder decoder: NSCoder) {
super.init(coder: decoder)
}
}
extension MainMenu {
private func setupUI() {
let appMenuItem = NSMenuItem()
appMenuItem.submenu = appMenu
addItem(appMenuItem)
}
private var appMenu: NSMenu {
let menu = NSMenu(title: "")
menu.addItem(title: "Quit \(applicationName)", keyEquivalent: "q") { [unowned self] in
self.eventHandler?(.quit)
}
return menu
}
}
Convenience extensions.
File NSMenu.swift
extension NSMenu {
#discardableResult
public func addItem(title: String, keyEquivalent: String, handler: NSMenuItem.Handler?) -> NSMenuItem {
let item = addItem(withTitle: title, action: nil, keyEquivalent: keyEquivalent)
item.setHandler(handler)
return item
}
}
File NSMenuItem.swift
extension NSMenuItem {
public typealias Handler = (() -> Void)
convenience init(title: String, keyEquivalent: String, handler: Handler?) {
self.init(title: title, action: nil, keyEquivalent: keyEquivalent)
setHandler(handler)
}
public func setHandler(_ handler: Handler?) {
target = self
action = #selector(wavelabsActionHandler(_:))
if let handler = handler {
ObjCAssociation.setCopyNonAtomic(value: handler, to: self, forKey: &OBJCAssociationKeys.actionHandler)
}
}
}
extension NSMenuItem {
private struct OBJCAssociationKeys {
static var actionHandler = "com.wavelabs.actionHandler"
}
#objc private func wavelabsActionHandler(_ sender: NSControl) {
guard sender == self else {
return
}
if let handler: Handler = ObjCAssociation.value(from: self, forKey: &OBJCAssociationKeys.actionHandler) {
handler()
}
}
}
File NSToolbar.swift
extension NSToolbar {
class GenericDelegate: NSObject, NSToolbarDelegate {
var selectableItemIdentifiers: [NSToolbarItem.Identifier] = []
var defaultItemIdentifiers: [NSToolbarItem.Identifier] = []
var allowedItemIdentifiers: [NSToolbarItem.Identifier] = []
var eventHandler: ((Event) -> Void)?
var makeItemCallback: ((_ itemIdentifier: NSToolbarItem.Identifier, _ willBeInserted: Bool) -> NSToolbarItem?)?
}
}
extension NSToolbar.GenericDelegate {
enum Event {
case willAddItem(item: NSToolbarItem, index: Int)
case didRemoveItem(item: NSToolbarItem)
}
}
extension NSToolbar.GenericDelegate {
func toolbar(_ toolbar: NSToolbar, itemForItemIdentifier itemIdentifier: NSToolbarItem.Identifier,
willBeInsertedIntoToolbar flag: Bool) -> NSToolbarItem? {
return makeItemCallback?(itemIdentifier, flag)
}
func toolbarDefaultItemIdentifiers(_: NSToolbar) -> [NSToolbarItem.Identifier] {
return defaultItemIdentifiers
}
func toolbarAllowedItemIdentifiers(_: NSToolbar) -> [NSToolbarItem.Identifier] {
return allowedItemIdentifiers
}
func toolbarSelectableItemIdentifiers(_: NSToolbar) -> [NSToolbarItem.Identifier] {
return selectableItemIdentifiers
}
// MARK: Notifications
func toolbarWillAddItem(_ notification: Notification) {
if let toolbarItem = notification.userInfo?["item"] as? NSToolbarItem,
let index = notification.userInfo?["newIndex"] as? Int {
eventHandler?(.willAddItem(item: toolbarItem, index: index))
}
}
func toolbarDidRemoveItem(_ notification: Notification) {
if let toolbarItem = notification.userInfo?["item"] as? NSToolbarItem {
eventHandler?(.didRemoveItem(item: toolbarItem))
}
}
}
File NSToolbarItem.swift
extension NSToolbarItem {
public typealias Handler = (() -> Void)
public func setHandler(_ handler: Handler?) {
target = self
action = #selector(wavelabsActionHandler(_:))
if let handler = handler {
ObjCAssociation.setCopyNonAtomic(value: handler, to: self, forKey: &OBJCAssociationKeys.actionHandler)
}
}
}
extension NSToolbarItem {
private struct OBJCAssociationKeys {
static var actionHandler = "com.wavelabs.actionHandler"
}
#objc private func wavelabsActionHandler(_ sender: NSControl) {
guard sender == self else {
return
}
if let handler: Handler = ObjCAssociation.value(from: self, forKey: &OBJCAssociationKeys.actionHandler) {
handler()
}
}
}
File ObjCAssociation.swift
public struct ObjCAssociation {
public static func value<T>(from object: AnyObject, forKey key: UnsafeRawPointer) -> T? {
return objc_getAssociatedObject(object, key) as? T
}
public static func setAssign<T>(value: T?, to object: Any, forKey key: UnsafeRawPointer) {
objc_setAssociatedObject(object, key, value, .OBJC_ASSOCIATION_ASSIGN)
}
public static func setRetainNonAtomic<T>(value: T?, to object: Any, forKey key: UnsafeRawPointer) {
objc_setAssociatedObject(object, key, value, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
public static func setCopyNonAtomic<T>(value: T?, to object: Any, forKey key: UnsafeRawPointer) {
objc_setAssociatedObject(object, key, value, .OBJC_ASSOCIATION_COPY_NONATOMIC)
}
public static func setRetain<T>(value: T?, to object: Any, forKey key: UnsafeRawPointer) {
objc_setAssociatedObject(object, key, value, .OBJC_ASSOCIATION_RETAIN)
}
public static func setCopy<T>(value: T?, to object: Any, forKey key: UnsafeRawPointer) {
objc_setAssociatedObject(object, key, value, .OBJC_ASSOCIATION_COPY)
}
}
The problem might be that you're still calling NSApplicationMain in your main function (in main.m). If you're not loading a nib such as MainMenu.nib, you'll probably have to rip out the call to NSApplicationMain and write your own code in main for starting the application.
Here is Casper's solution, updated for ARC as per Marco's suggestion:
#import <Cocoa/Cocoa.h>
#import "AppDelegate.h"
int main(int argc, char * argv[]) {
#autoreleasepool {
NSApplication *application = [NSApplication sharedApplication];
AppDelegate *appDelegate = [[AppDelegate alloc] init];
[application setDelegate:appDelegate];
[application run];
}
return EXIT_SUCCESS;
}
7 years too late to the party, but a bit simpler single file code
#import <Cocoa/Cocoa.h>
#interface AppDelegate : NSObject <NSApplicationDelegate, NSWindowDelegate> {
NSWindow* window;
}
#end
#implementation AppDelegate : NSObject
- (id)init {
if (self = [super init]) {
window = [NSWindow.alloc initWithContentRect: NSMakeRect(0, 0, 200, 200)
styleMask: NSWindowStyleMaskTitled | NSWindowStyleMaskClosable
backing: NSBackingStoreBuffered
defer: NO];
}
return self;
}
- (void)applicationWillFinishLaunching:(NSNotification *)notification {
window.title = NSProcessInfo.processInfo.processName;
[window cascadeTopLeftFromPoint: NSMakePoint(20,20)];
[window makeKeyAndOrderFront: self];
}
#end
int main(int argc, const char * argv[]) {
NSApplication* app = NSApplication.sharedApplication;
app.ActivationPolicy = NSApplicationActivationPolicyRegular;
NSMenuItem* item = NSMenuItem.new;
NSApp.mainMenu = NSMenu.new;
item.submenu = NSMenu.new;
[app.mainMenu addItem: item];
[item.submenu addItem: [[NSMenuItem alloc] initWithTitle: [#"Quit " stringByAppendingString: NSProcessInfo.processInfo.processName] action:#selector(terminate:) keyEquivalent:#"q"]];
AppDelegate* appDelegate = AppDelegate.new; // cannot collapse this and next line because .dlegate is weak
app.delegate = appDelegate;
(void)app.run;
return 0;
}
Of course, it's too late to answer on this but for anyone who is thinking on creating iOS App without Xib(Nib) files should keep this thing in mind.
Note: Although you can create an Objective-C application without using nib files, doing so is very rare and not recommended. Depending on your application, avoiding nib files might require you to replace large amounts of framework behavior to achieve the same results you would get using a nib file.
See this Documentation to know more what apple has to say on this approach
I hope this could help someone in future. Thanks!
The sample swift code for the autoreleasepool snippet provided above does not work in modern Xcode. Instead, you need to get rid of the #NSApplicationMain in your App Delegate source file, if there is one (Xcode now adds these for new projects), and add a main.swift file that contains the following:
The top level code sample above no longer works in recent versions of Xcode. Instead use this:
import Cocoa
let delegate = ExampleApplicationController() //alloc main app's delegate class
NSApplication.shared().delegate = delegate //set as app's delegate
let ret = NSApplicationMain(CommandLine.argc, CommandLine.unsafeArgv)
Don't use NSApplication and NSApp ...
You just have to specify the class which implements the UIApplicationDelegate protocol :
UIApplicationMain(argc, argv, nil, #"Name of your class");

Resources