Custom UIView with #IBInspectable func or Selector - xcode

I'm working with Swift and learning about #IBInspectable. I would like to know if it's possible create a custom view that allow other views and viewControllers to set a inner button action, as a ViewController links an IBAction directly to a button that it has.
I can create a #IBInspectable in my custom view with Selector type, but it's not visible to other classes in Interface Builder.
#IBInspectable private var touchUpInside =
NSSelectorFromString("didClickButton") {
didSet {
button.addTarget(self, action: "touchUpInside", forControlEvents: UIControlEvents.TouchUpInside)
}
}
But not visible
Thanks!

The only types that are IBInspectable are: booleans, strings, numbers (primitives and types), CGPoint, CGSize, CGRect, UIColor, NSRange, and UIImage.
NSSelector isn't a valid type.
Even if it was it wouldn't show up in the outlets inspector but the attributes inspector. You posted a screen shot of the outlets inspector.

Related

NSTableCellView inside a custom view refuses first responder

Instead of a TableView I want to use a NSTableCellView inside a custom view. So I created a xib file with a standard NSTableCellView and loaded that into a view.
The table cell view is displayed as expected. But I can't make its textfield the first responder. It's not responding to mouse events and it's not even reacting when I explicitly make it the first responder. makeFirstResponder returns true, but I see no blinking cursor and it doesn't respond to any key events.
Adding a regular textfield to the view does work however.
class ViewController: NSViewController
{
#IBOutlet var myView: NSView!
#IBOutlet var cellView: NSTableCellView! //ViewController is the file owner of the xib file
override func viewDidLoad()
{
super.viewDidLoad()
NSBundle(forClass: ViewController.self).loadNibNamed("Cell", owner: self, topLevelObjects: nil)
myview.addSubview(cellView)
cellView.layer?.borderColor = Color.blackColor().CGColor
cellView.layer?.borderWidth = 1
}
override func mouseDown(event: NSEvent)
{
let result = self.view.window?.makeFirstResponder(cellView.textField!)
print(result) //prints true
}
Why am I doing this? Well, I am trying to understand how a view-based table view would be implemented. It seems that at its core a tableview is just a view with lots of (probably cached and reused?) subviews. Also, I always thought that the number of views was limited for efficiency purposes on OS X?

Pass continuous colorwell data to second view controller

I have the following situation:
two ViewControllers each containing a box that is to be colored to a color picked from a color well in ViewController
The colorwell is set as continuous in order to see the changes reflected immediately
I am looking for a way to continuously pass the color well value on to the SecondViewController and on to a callback method that will color a box in the SecondViewController.
I found that the prepareForSegue method is commonly used to pass data between view controllers, this however only occurs once during the transition and not continuously.
Can someone point me out in the right direction? Googled for hours but I got really stuck with this.
Thanks.
import Cocoa
class ViewController: NSViewController {
#IBOutlet weak var box: NSBox!
#IBOutlet weak var well: NSColorWell!
#IBAction func well(sender: AnyObject) {
box.fillColor = well.color
}
override func prepareForSegue(segue: NSStoryboardSegue, sender: AnyObject?) {
let second = segue.destinationController as! SecondViewController
second.representedObject = well.color
}
}
import Cocoa
class SecondViewController: NSViewController {
#IBOutlet weak var box: NSBox!
override func viewWillAppear() {
// Note that box.fillColor requires box type to be custom
box.fillColor = self.representedObject as! NSColor
}
}
The prepareForSegue method is a chance to create links between two view controllers. It's pretty common for the source view controller to set itself up as the delegate of the destination view controller. It's also possible for the source view controller to save a reference to the destination view controller for future reference.
If you define a protocol with a method like
func colorValueHasChanged(newColor: NSColor)
Then you can use it in the IBAction for your color well to pass information about changes in the color well from one view controller to the other.

Subclass NSView instead of NSTableCellView

This question is an extension to this question.
I am working on Cocoa App, where I am populating a table using Cocoa Bindings.
I am subclassing NSView instead of NSTableCellView
As per NSTableCellView Class Referenence
If you use your own custom view cells that are not based on
NSTableCellView you should implement this property(objectValue) in order to be able
to receive changes to cell values.
Also
swift
var objectValue: AnyObject?
The objectValue is automatically set by the table when using bindings or is th...
This is my class implementation, which I will be using as cell in TableView
class TestView : NSView {
var objectValue: AnyObject?
init(nameA: String, nameB: String) {
super.init(frame: NSMakeRect(3, 3, 300, 40))
objectValue = nameA
let firstName = MyTextField(location: NSMakePoint(10, 10), stringVal: nameA)
self.addSubview(firstName)
let secondName = MyTextField(location: NSMakePoint(250, 10), stringVal: nameB)
self.addSubview(secondName)
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
}
Although having declared the objectValue in TestClass, I am still not able to bind the object value from IB
Custom views and objects don't get custom property bindings in IB. YOU HAVE TO SET UP BINDINGS IN CODE.
You also need to ensure your class does all the fun stuff required to enable bindings.

How to use dismiss an iPhone popover in an Adaptive Storyboard

I am new to iOS development, and am trying to learn storyboarding, Swift, and the new features of iOS 8 at the same time.
I have created a very simple storyboard that uses a Popover presentation segue to display another view. On the simulator, if I run this for an iPad, it works as expected. However, if I run it for an iPhone, instead of a popover, it displays a full-screen view, on top of the original view. This is fine; however, there is no way to dismiss it and go back to the original screen.
I have watched the WWDC 2014 video "228 A Look inside presentation controllers" and they can show a dismiss button if they build the user interface entirely with code.
I have also watched the "411 What's new in interface builder" session, where they say that this can be done in Interface Builder, but they do not show it, promising to show how to do it in the lab, if anyone is interested. Unfortunately, I did not attend WWDC 2014, or know anyone who has. My Google searches have not returned anything helpful either.
You could add the navigation controller like this-
Set your popover view controller as the root view controller to a navigation controller.
Delete the popover segue that you are currently using
Reconnect the segue from the button you are displaying the popover from to the navigation controller.
On iPad you will get a popover and on iPhone you will get a modal presentation. Both the iPad and iPhone will show the navigation controller. Depending on your use case this may or may not be something you want. Here's a screen show of what the storyboard should look like.
If you really do want your view controller to always be a popover leave your storyboard the way it is and add something like this to your view controller that presents the popover-
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"Your segue name"]) {
UIViewController *yourViewController = segue.destinationViewController;
yourViewController.modalPresentationStyle = UIModalPresentationPopover;
UIPopoverPresentationController *popoverPresentationController = yourViewController.popoverPresentationController;
popoverPresentationController.delegate = self;
}
}
The view controller presenting the popover will also need to respond to this UIPopoverPresentationDelegate method
- (UIModalPresentationStyle)adaptivePresentationStyleForPresentationController:(UIPresentationController *)controller
{
return UIModalPresentationNone;//always popover.
}
Lastly, you could do the following to only add the navigation controller to the modal presentation of your view controller on the iPhone and leave the popover on iPad without a navigation controller.
Leave your storyboard as is.
The proper place to inject the navigation controller is in - (UIViewController *)presentationController:(UIPresentationController *)controller viewControllerForAdaptivePresentationStyle:(UIModalPresentationStyle)style. In order for this to be called we must set ourselves as the delegate of the UIPopoverPresentationController.
Once again we will do this in prepareForSegue:
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"Your segue name"]) {
UIViewController *yourViewController = segue.destinationViewController;
yourViewController.modalPresentationStyle = UIModalPresentationPopover;
UIPopoverPresentationController *popoverPresentationController = yourViewController.popoverPresentationController;
popoverPresentationController.delegate = self;
}
}
Then we will do this in the delegate method that I mentioned above
-(UIViewController *)presentationController:(UIPresentationController *)controller viewControllerForAdaptivePresentationStyle:(UIModalPresentationStyle)style
{
UIViewController *presentedViewController = controller.presentedViewController;
UINavigationController *navigationController = [[UINavigationController alloc]
initWithRootViewController:presentedViewController];
UIBarButtonItem *dismissButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonItemStyleDone target:self action:#selector(done:)];
presentedViewController.navigationItem.rightBarButtonItem = dismissButton;
return navigationController;
}
Good Luck!
If what you want is a popover on your iPad but a modal sheet with a close button on your iPhone then you can do it without creating an extra navigation controller in storyboard for the popover.
In Xcode 6.3 storyboard, you simply hook up a view controller and designate the segue as a "Present as Popover"
The code below should go in the view controller that segues to the popover, not in the popover itself:
First you set up the popover delegate:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if (segue.identifier == "myPopoverSegueName") {
let vc = segue.destinationViewController
vc.popoverPresentationController?.delegate = self
return
}
}
Then you add the delegate extension (below your view controller's code) and create the navigation controller / close button on the fly:
extension myViewController: UIPopoverPresentationControllerDelegate {
func presentationController(controller: UIPresentationController, viewControllerForAdaptivePresentationStyle style: UIModalPresentationStyle) -> UIViewController? {
let btnDone = UIBarButtonItem(title: "Done", style: .Done, target: self, action: "dismiss")
let nav = UINavigationController(rootViewController: controller.presentedViewController)
nav.topViewController.navigationItem.leftBarButtonItem = btnDone
return nav
}
}
Then you add your dismiss function and you should be good to go:
func dismiss() {
self.dismissViewControllerAnimated(true, completion: nil)
}
I am not sure why you need to do storyboard setup for the Done button, all the work can be done programmatically with few lines of code. The important part is to implement some UIAdaptivePresentationControllerDelegate protocol methods exactly like below:
func adaptivePresentationStyleForPresentationController(controller: UIPresentationController) -> UIModalPresentationStyle
{
return .FullScreen
}
func presentationController(controller: UIPresentationController,
viewControllerForAdaptivePresentationStyle style: UIModalPresentationStyle) -> UIViewController?{
var navController:UINavigationController = UINavigationController(rootViewController: controller.presentedViewController)
controller.presentedViewController.navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.Done, target: self, action:"done")
return navController
}
Then, a simple method to implement the dismissing behavior for the popover in case it was presented in full screen:
func done (){
presentedViewController?.dismissViewControllerAnimated(true, completion: nil)
}
and you done!
In my case, I had a small popup that I wanted to be a popup on both an iPhone and iPad - and wanted to avoid using a navigation bar with a Dismiss. Discovered that one needed to implement two delegate calls (Swift 3.0):
extension MyViewController : UIPopoverPresentationControllerDelegate {
// Needed for iPhone popup
func adaptivePresentationStyle(for controller: UIPresentationController) -> UIModalPresentationStyle {
return .none
}
// Needed for iPhone in landscape
func adaptivePresentationStyle(for controller: UIPresentationController, traitCollection: UITraitCollection) -> UIModalPresentationStyle {
return .none
}
}
Its possible to do it with mimimal code whilst putting the logic into the storyboard instead. In the view controller that presents the popover, just put in the marker method
#IBAction func unwindToContainerVC(segue: UIStoryboardSegue) {
}
It does not need any code but needs to be present so you can control drag to the Exit icon later on when you use interface builder.
I have my popover content not take up the entire background view but have a small margin around it. This means you can use interface builder to create a tap gesture recogniser for this view. Control drag the gesture recogniser to the Exit icon which then pops up some Exit choices, one of which is the unwindToContainerVC method as seen above.
Now any tap around the edge (such as in an iPhone 4S scenario) takes you back to the presenting view controller.
Here is the connections inspector for the gesture recogniser:

Does NSView have a backgroundColor? If it does, I can't find it

I'm seeing all over the place online where people are referring to NSView's backgroundColor. I need to set a custom backgroundColor to my NSView, but I'm not seeing that property. I can't see it in code, or in IB. I am unable to set the background color of my simple NSView.
What could I be missing?
They must be thinking of UIView, which does have a backgroundColor property. NSView does not have a backgroundColor property.
You will have to achieve your effect some other way, e.g., through subclassing NSView.
Strangely enough NSView does not provide this (sigh). I use one consistent class I wrote myself throughout all my macOs projects. Just change the CustomView class in the Identity Inspector tab in IB to ColorView. You will then be able to set the background color in the Attribute Inspector, just like you would for a UIView. Here's the code, hope this helps!
import Cocoa
class ColorView: NSView
{
#IBInspectable var backgroundColor:NSColor?
required init?(coder decoder: NSCoder)
{
super.init(coder: decoder)
wantsLayer = true
}
override init(frame frameRect: NSRect)
{
super.init(frame: frameRect)
wantsLayer = true
}
override func layout()
{
layer?.backgroundColor = backgroundColor?.cgColor
}
}
You can reach it via the view's layer, e.g. (in Swift):
view.layer?.backgroundColor = NSColor.blue.cgColor
Strangely it can be set in the xib. In the identity inspector for the view, add the User Defined Runtime Attribute backgroundColor, with the Type color.

Resources