Border Doesn't Draw in NSView When inside NSTableCellView - cocoa

I have three NSViews inside an NSTableCellView. Depending on the data for the row, I want to show a selected role with a border on the NSView like this:
The blue border NSView is a subclass that looks like this:
class RolePill: NSView{
override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
layer?.cornerRadius = 9
layer?.borderWidth = 2
layer?.borderColor = NSColor.clear.cgColor
}
}
The role gets set initially when my table loads like this:
extension UsersVC: NSTableViewDelegate, NSTableViewDataSource{
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
let cell = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "UserCell"), owner: nil) as! UserCell
let user = users[row]
//Role
cell.setRole(role: user.role)
}
}
And my table cell, where the role gets set on load and on a button click, is set up like this:
class UserCell: NSTableCellView{
#IBOutlet weak var wrapOwner: RolePill!
#IBOutlet weak var wrapAdmin: RolePill!
#IBOutlet weak var wrapUser: RolePill!
#IBAction func clickOwner(_ sender: NSButton) {
setRole(role: "owner")
}
#IBAction func clickAdmin(_ sender: NSButton) {
setRole(role: "admin")
}
#IBAction func clickUser(_ sender: NSButton) {
setRole(role: "user")
}
func setRole(role: String){
let selectedColor = getAccentColor()
let offColor = Color(named: "BravoDark")!
let offTextColor = Color(named: "BFC0C2")
switch role{
case "owner":
wrapOwner.layer?.borderColor = selectedColor.cgColor
wrapAdmin.layer?.borderColor = offColor.cgColor
wrapUser.layer?.borderColor = offColor.cgColor
labelOwner.textColor = Color.white
labelAdmin.textColor = offTextColor
labelUser.textColor = offTextColor
case "admin":
wrapOwner.layer?.borderColor = offColor.cgColor
wrapAdmin.layer?.borderColor = selectedColor.cgColor
wrapUser.layer?.borderColor = offColor.cgColor
labelOwner.textColor = offTextColor
labelAdmin.textColor = Color.white
labelUser.textColor = offTextColor
default:
wrapOwner.layer?.borderColor = offColor.cgColor
wrapAdmin.layer?.borderColor = offColor.cgColor
wrapUser.layer?.borderColor = selectedColor.cgColor
labelOwner.textColor = offTextColor
labelAdmin.textColor = offTextColor
labelUser.textColor = Color.white
}
}
}
When the table loads initially, and anytime it refreshes (tableView.reloadData()) I lose my border and it looks like this:
As you can see, the textColor is set correctly to white. But for some reason, the border isn't set until I actually click on one of the IBAction buttons and manually trigger a change.
I suspect this is some kind of layer drawing bug where the RolePill class is redrawing every time my NSTableView reloads, but I don't know how to get it to accept the initial role state sent in tableView viewForRow.
Any idea what I'm doing wrong here? Thanks!

I was able to get this to work by setting all the NSView properties inside setRole() like this:
view.wantsLayer = true
view.layer?.cornerRadius = 11
view.layer?.borderWidth = 2
view.layer?.borderColor = getAccentColor().cgColor
label.textColor = Color.white
It seemed that the draw() method on my RolePill subclass was getting called frequently, so I couldn't set a border color in there since it doesn't know what the user's data state is.

Related

Additional view in NSCollectionViewItem pauses dragEvent in CollectionViewController

I am trying to implement drop delegates on a NSCollectionViewController and having issues using a custom NSCollectionViewItem with an additional View Layer I've added onto the CollectionView Item. FWIW, The additional view is used draw a dashed border to indicate a drop area.
The drag event works fine on this collectionItem, and all other collectionItems without this view when it is hidden, but as soon as the drag event occurs on top of this view, the drag event pauses.
The drag event resumes as soon as the mouse is dragged outside of the view, but nothing happens if I release the drag while the mouse is over the view.
I would love to know what is happening here and how to prevent the custom view from "stealing" the mouse event from the CollectionViewContoller.
Delegate Method on DropViewController
func collectionView(_ collectionView: NSCollectionView, validateDrop draggingInfo: NSDraggingInfo, proposedIndexPath proposedDropIndexPath: AutoreleasingUnsafeMutablePointer<NSIndexPath>, dropOperation proposedDropOperation: UnsafeMutablePointer<NSCollectionView.DropOperation>) -> NSDragOperation {
print("1")
if proposedDropIndexPath.pointee.item <= self.destinationDirectoryArray.count {
if proposedDropOperation.pointee == NSCollectionView.DropOperation.on {
return .move
}
} else if proposedDropIndexPath.pointee.item == self.destinationDirectoryArray.count {
//There's some stuff here validating the URL removed for brevity. It works okay when the focus is outside the view, but happy to add back in if helpful
if proposedDropOperation.pointee == NSCollectionView.DropOperation.on {
return .move
}
}
return[]
}
Configuring Collection View
func configureCollectionView() {
let flowLayout = NSCollectionViewFlowLayout()
flowLayout.minimumInteritemSpacing = 8.0
flowLayout.minimumLineSpacing = 8.0
destinationCollectionView.delegate = self
destinationCollectionView.dataSource = self
destinationCollectionView.register(NSNib(nibNamed: "DestinationCollectionItem", bundle: nil), forItemWithIdentifier: directoryItemIdentifier)
destinationCollectionView.collectionViewLayout = flowLayout
destinationCollectionView.registerForDraggedTypes([.fileURL])
destinationCollectionView.setDraggingSourceOperationMask(NSDragOperation.move, forLocal: true)
}
Collection View Item Setup
class DestinationCollectionItem: NSCollectionViewItem {
#IBOutlet weak var backgroundLayer: NSView!
override func viewDidLoad() {
super.viewDidLoad()
self.highlightState = .none
view.wantsLayer = true
view.layer?.cornerRadius = 8.0
backgroundLayer.isHidden = true
}
}
Custom Border View - Applied custom class in Xib and linked to File's Owner
class BorderedView: NSView {
override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
let path : NSBezierPath = NSBezierPath(roundedRect: self.bounds, xRadius: 10.0, yRadius: 10.0)
path.addClip()
let dashHeight: CGFloat = 2
let dashLength: CGFloat = 7
let dashColor: NSColor = .lightGray
// setup the context
let currentContext = NSGraphicsContext.current!.cgContext
currentContext.setLineWidth(dashHeight)
currentContext.setLineDash(phase: 0, lengths: [dashLength])
currentContext.setStrokeColor(dashColor.cgColor)
// draw the dashed path
let cgPath : CGPath = CGPath(roundedRect: NSRectToCGRect(self.bounds), cornerWidth: 10.0, cornerHeight: 10.0, transform: nil)
currentContext.addPath(cgPath)
currentContext.strokePath()
}
}
Well - I solved this one pretty quick.
While I previously tried adding unregisterDraggedTypes() to the backgroundLayer, the issue turned out to also be occurring on the image layer. I applied it to both the Image and backgroundLayer and it works now.
Collection View Item Setup
class DestinationCollectionItem: NSCollectionViewItem {
#IBOutlet weak var backgroundLayer: NSView!
override func viewDidLoad() {
super.viewDidLoad()
self.highlightState = .none
view.wantsLayer = true
view.layer?.cornerRadius = 8.0
backgroundLayer.isHidden = true
backgroundLayer.unregisterDraggedTypes()
self.imageView?.unregisterDraggedTypes()
self.textField?.unregisterDraggedTypes()
}
}

Reuse Code to set image

i´m developing an app that has a lot of view controllers and did set the back ground image with code, but i have to copy and paste that block of code in every view controller, how can i type it once and call it to reuse the code in every view controller..Thanks A lot.
var imageView: UIImageView!
let image = UIImage(named: "backGroundImage.png")!
override func loadView() {
super.loadView()
self.imageView = UIImageView(frame: CGRectZero)
self.imageView.contentMode = .ScaleAspectFill
self.imageView.image = image
self.view.addSubview(imageView)
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
self.imageView.frame = self.view.bounds
}
What you can do is make a parent UIViewController class and have all of your view controllers inherit from that class. Somethig like this:
class MyParentViewController: UIViewController {
var imageView: UIImageView!
let image = UIImage()
override func viewDidLoad() {
super.viewDidLoad()
self.imageView = UIImageView(frame: CGRectZero)
self.imageView.contentMode = .ScaleAspectFill
self.imageView.image = image
self.view.addSubview(imageView)
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
self.imageView.frame = self.view.bounds
}
}
And then your child view controllers that enherit from this class would look something like this:
class MyViewController: MyParentViewController {
overide func viewDidLoad() {
image = UIImage(named:"My image name")
super.viewDidLoad()
}
}
Now, here is really the way you should do it
However, having multiple view controllers that are all the same probably isnt a good idea. I would just keep only the parent view controller (rename it obviously) and just change the value of "image" in the segue that leads to the view to whatever image you want. This would eliminate the need to have a ton of view controllers, and will make it easier to add more images in the future if you want.
Here's how I would do it:
protocol ImageViewBackground {
var backgroundImageView: UIImageView? {get set}
mutating func setBackgroundImage(image: UIImage)
}
extension ImageViewBackground where Self: UIViewController {
mutating func setBackgroundImage(image: UIImage) {
let backgroundImageView = self.backgroundImageView ?? setupImageView()
backgroundImageView.image = image
}
mutating func setupImageView() -> UIImageView {
let imageView = UIImageView(frame: self.view.frame)
imageView.contentMode = .ScaleAspectFill
view.addSubview(imageView)
backgroundImageView = backgroundImageView
return imageView
}
}
Then just make any UIViewController adhere to the protocol like so:
class ViewController: UIViewController, ImageViewBackground {
var backgroundImageView: UIImageView?
}
Then you can call setBackgroundImage from anywhere in your VC without having to make a generic subclass just for the background image.

custom datasource method is not calling in swift 2.0

here is my BorderView and The task I'm trying to do is to hookup the custom object (Border) to the main storyboard.
import UIKit
protocol BorderViewDataSource: class{
func colorForBorderView(sender:BorderView) -> String? }
class BorderView: UIView {
#IBOutlet weak var topLeftImage: UIImageView!
#IBOutlet weak var topRightImage: UIImageView!
#IBOutlet weak var bottomLeftImage: UIImageView!
#IBOutlet weak var bottomRightImage: UIImageView!
var view: UIView!
weak var dataSource:BorderViewDataSource?
override init(frame: CGRect) {
super.init(frame: frame)
xtraSetup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
xtraSetup()
}
func xtraSetup() {
view = loadViewFromNib()
// use bounds not frame or it'll be offset
view.frame = bounds
// Make the view stretch with containing view
view.autoresizingMask = [UIViewAutoresizing.FlexibleWidth, UIViewAutoresizing.FlexibleHeight]
// Adding custom subview on top of our view (over any custom drawing > see note below)
addSubview(view)
let borderColor = dataSource?.colorForBorderView(self) ?? "red"
applyColor(borderColor)
}
// this is an actual load from nib module
func loadViewFromNib() -> UIView {
let bundle = NSBundle(forClass: self.dynamicType)
let nib = UINib(nibName: "BorderView", bundle: bundle)
let view = nib.instantiateWithOwner(self, options: nil)[0] as! UIView
return view
}
func applyColor(borderColor:String){
var imageName = "border-topleft-\(borderColor)"
topLeftImage.image = UIImage(named: imageName)
imageName = "border-topright-\(borderColor)"
print("Border color is \(borderColor), and Last image name is \(imageName)")
}
}
So when i open the app it will show the image of different color depending on what the value of the color is set in the ViewContorller.swift and here is my viewcontroller. swift code
class ViewController: UIViewController, BorderViewDataSource {
var borderType = 2
#IBOutlet weak var borderView: BorderView!{
didSet{
borderView.dataSource = self
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func colorForBorderView(sender: BorderView) -> String? {
var borderColor = "blue"
switch borderType {
case 1: borderColor = "blue"
case 2: borderColor = "purple"
case 3: borderColor = "red"
default: break
}
print("Border Type is \(borderType), and border color is \(borderColor)")
return borderColor
}
}
but colorForBorderView method is not calling when i running the app
It seems when the view is initialized, the dataSource property of that view is nil. And you only call that method during initialization of your view. You can apply the color after viewDidLoad() in your view controller
From the documentation:
When you assign a default value to a stored property, or set its
initial value within an initializer, the value of that property is set
directly, without calling any property observers.
That's the reason why the datasource is not set. Put the line borderView.dataSource = self into viewDidLoad()

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