Related
I would use a UILabel to allow users to select a date with UIDatePicker.
To do this, I created an UILabel subclass overwriting the inputView and the inputAccessoryView properties making them writable; I also implemented the -(BOOL) canBecomeFirstResponder and the -(BOOL) isUserInteractionEnabled methods returning YES for both.
Then I assigned an instance of UIDatePIcker to the inputView property.
At this point my expectation is that when the label is tapped an UIDatePicker should appear, but nothing happens.
Any help?
This is the code:
YPInteractiveUILabel.h
#interface YPInteractiveUILabel : UILabel
#property (readwrite) UIView *inputView;
#property (readwrite) UIView *inputAccessoryView;
- (BOOL) canBecomeFirstResponder;
- (BOOL) isUserInteractionEnabled;
#end
YPInteractiveUILabel.h
#import "YPInteractiveUILabel.h"
#implementation YPInteractiveUILabel
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self)
{
UIDatePicker *datePicker = [[UIDatePicker alloc] init];
[self setInputView:datePicker];
}
return self;
}
- (BOOL)isUserInteractionEnabled
{
return YES;
}
- (BOOL)canBecomeFirstResponder
{
return YES;
}
#end
UILabel + UIDatePicker -- Swift version with Done button.
import UIKit
class DatePickerLabel: UILabel {
private let _inputView: UIView? = {
let picker = UIDatePicker()
return picker
}()
private let _inputAccessoryToolbar: UIToolbar = {
let toolBar = UIToolbar()
toolBar.barStyle = UIBarStyle.Default
toolBar.translucent = true
toolBar.sizeToFit()
return toolBar
}()
override var inputView: UIView? {
return _inputView
}
override var inputAccessoryView: UIView? {
return _inputAccessoryToolbar
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
let doneButton = UIBarButtonItem(title: "Done", style: UIBarButtonItemStyle.Plain, target: self, action: #selector(doneClick))
let spaceButton = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.FlexibleSpace, target: nil, action: nil)
_inputAccessoryToolbar.setItems([ spaceButton, doneButton], animated: false)
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(launchPicker))
self.addGestureRecognizer(tapRecognizer)
}
override func canBecomeFirstResponder() -> Bool {
return true
}
#objc private func launchPicker() {
becomeFirstResponder()
}
#objc private func doneClick() {
resignFirstResponder()
}
}
You can just ovveride inputView getter method, like explained in Apple documentation:
- (UIView *)inputView {
return myInputView;
}
- (BOOL)canBecomeFirstResponder {
return YES;
}
Then add a gesture or a button to call becomeFirstResponder:
- (void)showInputView:(id)sender {
[self becomeFirstResponder];
}
How about something like this. Rather than subclass the label, just add a gesture recognizer to it, and bring up the picker in the tap recognizer's handler. In the picker's action method, populate the label and dismiss the picker. This example works, but you'd probably want to add some animation to make it look better:
- (void)viewDidLoad {
[super viewDidLoad];
self.label.userInteractionEnabled = YES;
UITapGestureRecognizer *tapper = [[UITapGestureRecognizer alloc]initWithTarget:self action:#selector(launchPicker:)];
[self.label addGestureRecognizer:tapper];
}
-(void)launchPicker:(UITapGestureRecognizer *) tapper {
UIDatePicker *picker = [[UIDatePicker alloc] initWithFrame:CGRectMake(5, 150, 300, 200)];
[picker addTarget:self action:#selector(updateLabel:) forControlEvents:UIControlEventValueChanged];
[self.view addSubview:picker];
}
-(IBAction)updateLabel:(UIDatePicker *)sender {
self.label.text = [NSString stringWithFormat:#"%#",sender.date ];
[sender removeFromSuperview];
}
Thanks to the suggestions (especially the comment from NeverBe and the answer proposed by rdelmar) I found the problem in my code.
In brief, in order to show the input label, a call to the becomeFirstResponder method when the user tap the label is needed.
Following the UILabel subclass implementation corrected (the header file remains the same):
#implementation YPInteractiveUILabel
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self)
{
UIDatePicker *datePicker = [[UIDatePicker alloc] init];
[self setInputView:datePicker];
UITapGestureRecognizer *tapper = [[UITapGestureRecognizer alloc]initWithTarget:self action:#selector(launchPicker:)];
[self addGestureRecognizer:tapper];
}
return self;
}
- (BOOL)isUserInteractionEnabled
{
return YES;
}
- (BOOL)canBecomeFirstResponder
{
return YES;
}
-(void)launchPicker:(UITapGestureRecognizer *) tapper
{
[self becomeFirstResponder];
}
#end
This is a code snipet of #dmitriy-kirakosyan updated to Swift 5
class DatePickerLabel: UILabel {
private let _inputView: UIView? = {
let picker = UIDatePicker()
return picker
}()
private let _inputAccessoryToolbar: UIToolbar = {
let toolBar = UIToolbar()
toolBar.barStyle = UIBarStyle.default
toolBar.isTranslucent = true
toolBar.sizeToFit()
return toolBar
}()
override var inputView: UIView? {
return _inputView
}
override var inputAccessoryView: UIView? {
return _inputAccessoryToolbar
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
let doneButton = UIBarButtonItem(title: "Done", style: UIBarButtonItem.Style.plain, target: self, action: #selector(doneClick))
let spaceButton = UIBarButtonItem(barButtonSystemItem: UIBarButtonItem.SystemItem.flexibleSpace, target: nil, action: nil)
_inputAccessoryToolbar.setItems([ spaceButton, doneButton], animated: false)
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(launchPicker))
self.addGestureRecognizer(tapRecognizer)
}
override var canBecomeFirstResponder: Bool {
return true
}
#objc private func launchPicker() {
becomeFirstResponder()
}
#objc private func doneClick() {
resignFirstResponder()
}
}
I know this is an old question but this might still be useful to someone.
There is another way to solve this - there is no need to complicate things with gesture recognizers...
GTPDateLabel.h
#interface GTPDateLabel : UILabel
#property (readonly, retain) UIView *inputView;
#end
GTPDateLabel.m
#import "GTPDateLabel.h"
#implementation GTPDateLabel
#synthesize inputView = _inputView;
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
self.userInteractionEnabled = YES;
}
return self;
}
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self)
{
self.userInteractionEnabled = YES;
}
return self;
}
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesEnded:touches withEvent:event];
[self becomeFirstResponder];
}
- (BOOL)canBecomeFirstResponder
{
return YES;
}
-(UIView *)inputView
{
if (!_inputView)
{
UIDatePicker *datePicker = [[UIDatePicker alloc] init];
_inputView = datePicker;
}
return _inputView;
}
#end
Note that you should also set the delegate and in case of custom UIPicker also dataSource...
Here is the UILabel that shows PickerView, in Swift 4
final class DatePickerLabel: UILabel {
private let pickerView: UIPickerView
private let toolbar: UIToolbar
required init(pickerView: UIPickerView, toolbar: UIToolbar) {
self.pickerView = pickerView
self.toolbar = toolbar
super.init(frame: .zero)
let recogniser = UITapGestureRecognizer(target: self, action: #selector(tapped))
addGestureRecognizer(recogniser)
}
required init?(coder aDecoder: NSCoder) {
fatalError()
}
override var inputView: UIView? {
return pickerView
}
override var inputAccessoryView: UIView? {
return toolbar
}
override var canBecomeFirstResponder: Bool {
return true
}
#objc private func tapped() {
becomeFirstResponder()
}
}
I know this is possible, saw this in some apps (iGotYa is I believe the most famous).
I know how to set up everything for taking photos, saving it and everything.
But how can it be done programmatically? just having the user click some button (in the regular view controller) and it will automatically take a picture using the front camera and saving it (or not, just getting it as a UIImage)
Thanks!
This is very simple, just use the AVFoundation reference guide:
https://developer.apple.com/library/ios/#documentation/AudioVideo/Conceptual/AVFoundationPG/Articles/04_MediaCapture.html
If you don't want the user to see the preview input you can just skip the set preview layer part of the code.
Edit: To be more detailed.
1)You set your capture configuration using the AVFoundation.
Set the camera input to frontal, turn off flash etc etc.
2)You SKIP the part where the video preview layer is set.
3)You call the captureStillImageAsynchronouslyFromConnection:completionHandler: method whenever you want the picture to be taken.
Note: If you want the flash to not be heard and such then you might be violating the user rights in some countries (japan for example). One workaround I know of to do so is by capturing a frame of a video (does not trigger flash).
You can also do it without AVFoundation and it is in my opinion an easier way to implement it using only the UIImagePickerController.
There are 3 conditions:
Obviously the device needs to have a camera
You must hide the camera controls
Then simply use the takePicture method from UIImagePickerController
Below is a simple example that you woul typically trigger after a button push
- (IBAction)takePhoto:(id)sender
{
UIImagePickerController *picker = [[UIImagePickerController alloc] init];
picker.delegate = self;
picker.sourceType = UIImagePickerControllerSourceTypeCamera;
picker.cameraDevice = UIImagePickerControllerCameraDeviceFront;
picker.showsCameraControls = NO;
[self presentViewController:picker animated:YES
completion:^ {
[picker takePicture];
}];
}
VLBCameraView is a library that uses AVFoundation to take photo.
A preview is shown in the view, which you can then call the method VLBCameraView#takePicture programmatically to take a photo.
Comes with CocoaPods.
Here is the code for Objective -C Custom Camera. You can add features,
buttons according to your wish.
#import "CustomCameraVC.h"
#interface CustomCameraVC () {
BOOL frontCamera;
}
#property (strong,nonatomic) AVCaptureSession *captureSession;
#property (strong,nonatomic) AVCaptureStillImageOutput *stillImageOutput;
#property (strong,nonatomic) AVCaptureVideoPreviewLayer *videoPreviewLayer;
#property (weak, nonatomic) IBOutlet UIView *viewCamera;
#end
#implementation CustomCameraVC
- (void)viewDidLoad {
[super viewDidLoad];
}
-(void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:YES];
frontCamera = NO;
[self showCameraWithFrontCamera:frontCamera];
}
-(void)showCameraWithFrontCamera:(BOOL)flag {
self.captureSession = [[AVCaptureSession alloc]init];
self.captureSession.sessionPreset = AVCaptureSessionPresetPhoto;
AVCaptureDevice *captureDevice;
if(flag) {
captureDevice= [self frontCamera];
}
else {
captureDevice= [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
}
NSError *error = nil;
AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice:captureDevice error:&error];
[self.captureSession addInput:input];
self.stillImageOutput = [AVCaptureStillImageOutput new];
self.stillImageOutput.outputSettings = #{AVVideoCodecKey:AVVideoCodecJPEG};
[self.captureSession addOutput:_stillImageOutput];
self.videoPreviewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:self.captureSession];
self.videoPreviewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
self.videoPreviewLayer.connection.videoOrientation = AVCaptureVideoOrientationPortrait;
[self.viewCamera.layer addSublayer:self.videoPreviewLayer];
[self.captureSession startRunning];
self.videoPreviewLayer.frame = self.viewCamera.bounds;
}
- (AVCaptureDevice *)frontCamera {
NSArray *devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
for (AVCaptureDevice *device in devices) {
if ([device position] == AVCaptureDevicePositionFront) {
return device;
}
}
return nil;
}
- (IBAction)btnCaptureImagePressed:(id)sender {
AVCaptureConnection * videoConnection = [_stillImageOutput connectionWithMediaType:AVMediaTypeVideo];
[_stillImageOutput captureStillImageAsynchronouslyFromConnection:videoConnection completionHandler:^(CMSampleBufferRef _Nullable sampleBuffer, NSError * _Nullable error) {
NSData *imageData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:sampleBuffer];
UIImage *image = [[UIImage alloc]initWithData: imageData];
UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil);
}];
}
#end
Swift 5.2
For reference
https://gist.github.com/hadanischal/33054429b18287c12ed4f4b8d45a1701
Info.plist
<key>NSCameraUsageDescription</key>
<string>Access camera</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>Access PhotoLibrary</string>
AVFoundationHelper
import AVFoundation
enum CameraStatus {
case notDetermined
case restricted
case denied
case authorized
}
protocol AVFoundationHelperProtocol: AnyObject {
// MARK: - Check and Respond to Camera Authorization Status
var authorizationStatus: CameraStatus { get }
// MARK: - Request Camera Permission
func requestAccess(completionHandler handler: #escaping (Bool) -> Void)
}
final class AVFoundationHelper: AVFoundationHelperProtocol {
// MARK: - Check and Respond to Camera Authorization Status
var authorizationStatus: CameraStatus {
let cameraAuthorizationStatus = AVCaptureDevice.authorizationStatus(for: .video)
switch cameraAuthorizationStatus {
case .notDetermined:
return CameraStatus.notDetermined
case .authorized:
return CameraStatus.authorized
case .restricted:
return CameraStatus.restricted
case .denied:
return CameraStatus.denied
#unknown default:
return CameraStatus.notDetermined
}
}
// MARK: - Request Camera Permission
func requestAccess(completionHandler handler: #escaping (Bool) -> Void) {
AVCaptureDevice.requestAccess(for: .video, completionHandler: { accessGranted in
handler(accessGranted)
})
}
}
ViewController
import UIKit
final class ViewController: UIViewController {
#IBOutlet var cameraAccessButton: UIButton!
#IBOutlet var photoImageView: UIImageView!
private var model: AVFoundationHelperProtocol = AVFoundationHelper()
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func cameraButtonPressed(_: Any) {
let status = model.authorizationStatus
switch status {
case .notDetermined:
model.requestAccess { hasAccess in
if hasAccess {
DispatchQueue.main.async {
self.showCameraReader()
}
} else {
self.alertCameraAccessNeeded()
}
}
case .restricted, .denied:
alertCameraAccessNeeded()
case .authorized:
showCameraReader()
}
}
private func alertCameraAccessNeeded() {
let appName = "This app Name"
let alert = UIAlertController(title: "This feature requires Camera Access",
message: "In iPhone settings, tap \(appName) and turn on Camera access",
preferredStyle: UIAlertController.Style.alert)
let actionSettings = UIAlertAction(title: "Settings", style: .default, handler: { _ -> Void in
guard let settingsAppURL = URL(string: UIApplication.openSettingsURLString) else { return }
UIApplication.shared.open(settingsAppURL)
})
let actionCancel = UIAlertAction(title: "Cancel", style: .destructive, handler: { _ -> Void in
})
alert.addAction(actionSettings)
alert.addAction(actionCancel)
present(alert, animated: true, completion: nil)
}
}
extension ViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate {
private func showCameraReader() {
if UIImagePickerController.isSourceTypeAvailable(.camera) {
let imagePicker = UIImagePickerController()
imagePicker.sourceType = .camera
imagePicker.allowsEditing = true
imagePicker.delegate = self
present(imagePicker, animated: true)
} else if UIImagePickerController.isSourceTypeAvailable(.photoLibrary) {
let imagePicker = UIImagePickerController()
imagePicker.sourceType = .photoLibrary
imagePicker.allowsEditing = true
imagePicker.delegate = self
present(imagePicker, animated: true)
} else {
// TODO: Implement proper alert
alertCameraAccessNeeded()
}
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) {
picker.dismiss(animated: true)
guard let image = info[.editedImage] as? UIImage else {
print("No image found")
return
}
photoImageView.image = image
// print out the image size as a test
print(image.size)
}
}
Objective C
In .h file
#interface ABCViewController : UIViewController
#property (strong, nonatomic) IBOutlet UIImageView *imageView;
- (IBAction)takePhoto: (UIButton *)sender;
- (IBAction)selectPhoto:(UIButton *)sender;
#end
In .m file
#interface ABCViewController : UIViewController <UIImagePickerControllerDelegate, UINavigationControllerDelegate>
- (IBAction)takePhoto:(UIButton *)sender {
if (![UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) {
UIAlertView *myAlertView = [[UIAlertView alloc] initWithTitle:#"Error"
message:#"Device has no camera"
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles: nil];
[myAlertView show];
} else {
UIImagePickerController *picker = [[UIImagePickerController alloc] init];
picker.delegate = self;
picker.allowsEditing = YES;
picker.sourceType = UIImagePickerControllerSourceTypeCamera;
[self presentViewController:picker animated:YES completion:NULL];
}
}
- (IBAction)selectPhoto:(UIButton *)sender {
UIImagePickerController *picker = [[UIImagePickerController alloc] init];
picker.delegate = self;
picker.allowsEditing = YES;
picker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
[self presentViewController:picker animated:YES completion:NULL];
}
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info {
UIImage *chosenImage = info[UIImagePickerControllerEditedImage];
self.imageView.image = chosenImage;
[picker dismissViewControllerAnimated:YES completion:NULL];
}
- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker {
[picker dismissViewControllerAnimated:YES completion:NULL];
}
Here's a C# implementation for the same task, for Xamarin iOS.
public void TakePhoto(Action<byte[]> getImageBytesAction)
{
var picker = new UIImagePickerController();
picker.PrefersStatusBarHidden();
picker.SourceType = UIImagePickerControllerSourceType.Camera;
picker.CameraDevice = UIImagePickerControllerCameraDevice.Rear;
picker.ShowsCameraControls = false;
picker.CameraCaptureMode = UIImagePickerControllerCameraCaptureMode.Photo;
picker.CameraFlashMode = UIImagePickerControllerCameraFlashMode.Off;
picker.FinishedPickingMedia += (object sender, UIImagePickerMediaPickedEventArgs e) =>
{
var photo = e.OriginalImage;
picker.DismissModalViewController(false);
//var imageSource = ImageSource.FromStream(() => photo.AsJPEG().AsStream());
//invoke the action when finished taking picture
var correctedImage = new UIImage(photo.CGImage, 2.0f, UIImageOrientation.Up); //scaling 2.0 makes image half
getImageBytesAction.Invoke(correctedImage.AsJPEG().ToArray());
};
//open photo picker with TakePicture trigger on load completion (added a delay to let the camera adjust light and focus)
GetTopViewController().PresentViewController(picker, false, async () => { await Task.Delay(1000); picker.TakePicture(); });
}
I am presenting a modal view controller as a form sheet and dismissing it when the cancel button, which is a bar button item, is clicked. I need to dismiss it when I tap on outside of that view. Please help me with a reference. Note: my modal view controller is presented with a navigation controller.
#cli_hlt, #Bill Brasky thanks for your answer. I need to dismiss it when tap occurs outside of the modal view which is a form sheet. I am pasting my code below.
-(void)gridView:(AQGridView *)gridView didSelectItemAtIndex:(NSUInteger)index
{
if(adminMode)
{
CHEditEmployeeViewController *editVC = [[CHEditEmployeeViewController alloc] initWithNibName:#"CHEditEmployeeViewController" bundle:nil];
editVC.delegate = self;
editVC.pickedEmployee = employee;
editVC.edit = TRUE;
editVC.delegate = self;
UINavigationController *navigationController = [[UINavigationController alloc]initWithRootViewController:editVC];
navigationController.modalPresentationStyle = UIModalPresentationFormSheet;
[self presentModalViewController:navigationController animated:YES];
return;
} //the above code is from the view controller which presents the modal view. Please look at the below code too which is from my modal view controller. Please guide me in a proper way. -(void)tapGestureRecognizer {
UITapGestureRecognizer *recognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleTapBehind:)];
[recognizer setNumberOfTapsRequired:1];
recognizer.cancelsTouchesInView = NO; //So the user can still interact with controls in the modal view
[self.view addGestureRecognizer:recognizer];
}
- (void)handleTapBehind:(UITapGestureRecognizer *)sender
{
if (sender.state == UIGestureRecognizerStateEnded)
{
CGPoint location = [sender locationInView:nil]; //Passing nil gives us coordinates in the window
//Then we convert the tap's location into the local view's coordinate system, and test to see if it's in or outside. If outside, dismiss the view.
if (![self.view pointInside:[self.view convertPoint:location fromView:self.view.window] withEvent:nil])
{
[self dismissModalViewControllerAnimated:YES];
[self.view.window removeGestureRecognizer:sender];
}
}
}
I know this is an old question but this IS possible, despite of what the "right" answer says. Since this was the first result when I was looking for this I decided to elaborate:
This is how you do it:
You need to add a property to the View Controller from where you want to present modally, in my case "tapBehindGesture".
then in viewDidAppear
if(!tapBehindGesture) {
tapBehindGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(tapBehindDetected:)];
[tapBehindGesture setNumberOfTapsRequired:1];
[tapBehindGesture setCancelsTouchesInView:NO]; //So the user can still interact with controls in the modal view
}
[self.view.window addGestureRecognizer:tapBehindGesture];
And Here is the implementation for tapBehindDetected
- (void)tapBehindDetected:(UITapGestureRecognizer *)sender
{
if (sender.state == UIGestureRecognizerStateEnded)
{
//(edited) not working for ios8 above
//CGPoint location = [sender locationInView:nil]; //Passing nil gives us coordinates in the window
CGPoint location = [sender locationInView: self.presentingViewController.view];
//Convert tap location into the local view's coordinate system. If outside, dismiss the view.
if (![self.presentedViewController.view pointInside:[self.presentedViewController.view convertPoint:location fromView:self.view.window] withEvent:nil])
{
if(self.presentedViewController) {
[self dismissViewControllerAnimated:YES completion:nil];
}
}
}
}
Just remember to remove tapBehindGesture from view.window on viewWillDisappear to avoid triggering handleTapBehind in an unallocated object.
I solved iOS 8 issue by adding delegate to gesture recognizer
[taprecognizer setDelegate:self];
with these responses
#pragma mark - UIGestureRecognizer Delegate
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
return YES;
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
return YES;
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
return YES;
}
that works for me with iOS 8 GM
Swift 4 version that works in both portrait and landscape - no swapping of x, y required.
class TapBehindModalViewController: UIViewController, UIGestureRecognizerDelegate {
private var tapOutsideRecognizer: UITapGestureRecognizer!
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if (self.tapOutsideRecognizer == nil) {
self.tapOutsideRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.handleTapBehind))
self.tapOutsideRecognizer.numberOfTapsRequired = 1
self.tapOutsideRecognizer.cancelsTouchesInView = false
self.tapOutsideRecognizer.delegate = self
self.view.window?.addGestureRecognizer(self.tapOutsideRecognizer)
}
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
if(self.tapOutsideRecognizer != nil) {
self.view.window?.removeGestureRecognizer(self.tapOutsideRecognizer)
self.tapOutsideRecognizer = nil
}
}
func close(sender: AnyObject) {
self.dismiss(animated: true, completion: nil)
}
// MARK: - Gesture methods to dismiss this with tap outside
#objc func handleTapBehind(sender: UITapGestureRecognizer) {
if (sender.state == UIGestureRecognizerState.ended) {
let location: CGPoint = sender.location(in: self.view)
if (!self.view.point(inside: location, with: nil)) {
self.view.window?.removeGestureRecognizer(sender)
self.close(sender: sender)
}
}
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
}
As far as I can tell none of the answer seem to be working right away in every condition.
My solution (either inherit from it or paste it in):
#interface MyViewController () <UIGestureRecognizerDelegate>
#property (strong, nonatomic) UITapGestureRecognizer *tapOutsideRecognizer;
#end
-(void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
if (!self.tapOutsideRecognizer) {
self.tapOutsideRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleTapBehind:)];
self.tapOutsideRecognizer.numberOfTapsRequired = 1;
self.tapOutsideRecognizer.cancelsTouchesInView = NO;
self.tapOutsideRecognizer.delegate = self;
[self.view.window addGestureRecognizer:self.tapOutsideRecognizer];
}
}
-(void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
// to avoid nasty crashes
if (self.tapOutsideRecognizer) {
[self.view.window removeGestureRecognizer:self.tapOutsideRecognizer];
self.tapOutsideRecognizer = nil;
}
}
#pragma mark - Actions
- (IBAction)close:(id)sender
{
[self dismissViewControllerAnimated:YES completion:nil];
}
- (void)handleTapBehind:(UITapGestureRecognizer *)sender
{
if (sender.state == UIGestureRecognizerStateEnded)
{
CGPoint location = [sender locationInView:nil]; //Passing nil gives us coordinates in the window
//Then we convert the tap's location into the local view's coordinate system, and test to see if it's in or outside. If outside, dismiss the view.
if (![self.view pointInside:[self.view convertPoint:location fromView:self.view.window] withEvent:nil])
{
// Remove the recognizer first so it's view.window is valid.
[self.view.window removeGestureRecognizer:sender];
[self close:sender];
}
}
}
#pragma mark - Gesture Recognizer
// because of iOS8
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer{
return YES;
}
Here is my version that works for iOS 7 and iOS 8 and does not require conditional swapping of coordinates:
- (void)handleTapBehind:(UITapGestureRecognizer *)sender
{
if (sender.state == UIGestureRecognizerStateEnded)
{
CGPoint location = [sender locationInView:self.view];
if (![self.view pointInside:location withEvent:nil]) {
[self.view.window removeGestureRecognizer:self.recognizer];
[self dismissViewControllerAnimated:YES completion:nil];
}
}
}
For iOS 8, you must both implement the UIGestureRecognizer per Martino's answer, and swap the (x,y) coordinates of the tapped location when in landscape orientation. Not sure if this is due to an iOS 8 bug.
- (void) viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
// add gesture recognizer to window
UITapGestureRecognizer *recognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleTapBehind:)];
[recognizer setNumberOfTapsRequired:1];
recognizer.cancelsTouchesInView = NO; //So the user can still interact with controls in the modal view
[self.view.window addGestureRecognizer:recognizer];
recognizer.delegate = self;
}
- (void)handleTapBehind:(UITapGestureRecognizer *)sender
{
if (sender.state == UIGestureRecognizerStateEnded) {
// passing nil gives us coordinates in the window
CGPoint location = [sender locationInView:nil];
// swap (x,y) on iOS 8 in landscape
if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(#"8.0")) {
if (UIInterfaceOrientationIsLandscape([UIApplication sharedApplication].statusBarOrientation)) {
location = CGPointMake(location.y, location.x);
}
}
// convert the tap's location into the local view's coordinate system, and test to see if it's in or outside. If outside, dismiss the view.
if (![self.view pointInside:[self.view convertPoint:location fromView:self.view.window] withEvent:nil]) {
// remove the recognizer first so it's view.window is valid
[self.view.window removeGestureRecognizer:sender];
[self dismissViewControllerAnimated:YES completion:nil];
}
}
}
#pragma mark - UIGestureRecognizer Delegate
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
return YES;
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
return YES;
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
return YES;
}
Based on Bart van Kuik's answer and NavAutoDismiss and other great snippets here.
class DismissableNavigationController: UINavigationController, UIGestureRecognizerDelegate {
private var tapOutsideRecognizer: UITapGestureRecognizer!
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
if tapOutsideRecognizer == nil {
tapOutsideRecognizer = UITapGestureRecognizer(target: self, action: #selector(DismissableNavigationController.handleTapBehind))
tapOutsideRecognizer.numberOfTapsRequired = 1
tapOutsideRecognizer.cancelsTouchesInView = false
tapOutsideRecognizer.delegate = self
view.window?.addGestureRecognizer(tapOutsideRecognizer)
}
}
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
if tapOutsideRecognizer != nil {
view.window?.removeGestureRecognizer(tapOutsideRecognizer)
tapOutsideRecognizer = nil
}
}
func close(sender: AnyObject) {
dismissViewControllerAnimated(true, completion: nil)
}
func handleTapBehind(sender: UITapGestureRecognizer) {
if sender.state == UIGestureRecognizerState.Ended {
var location: CGPoint = sender.locationInView(nil)
if UIInterfaceOrientationIsLandscape(UIApplication.sharedApplication().statusBarOrientation) {
location = CGPoint(x: location.y, y: location.x)
}
if !view.pointInside(view.convertPoint(location, fromView: view.window), withEvent: nil) {
view.window?.removeGestureRecognizer(sender)
close(sender)
}
}
}
func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWithGestureRecognizer otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
}
Usage:
let vc = MyViewController()
let nc = DismissableNavigationController(rootViewController: vc)
nc.modalPresentationStyle = UIModalPresentationStyle.FormSheet
presentViewController(nc, animated: true, completion: nil)
#yershuachu's answer, in Swift 2:
class ModalParentViewController: UIViewController, UIGestureRecognizerDelegate {
private var tapOutsideRecognizer: UITapGestureRecognizer!
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
if(self.tapOutsideRecognizer == nil) {
self.tapOutsideRecognizer = UITapGestureRecognizer(target: self, action: "handleTapBehind:")
self.tapOutsideRecognizer.numberOfTapsRequired = 1
self.tapOutsideRecognizer.cancelsTouchesInView = false
self.tapOutsideRecognizer.delegate = self
self.view.window?.addGestureRecognizer(self.tapOutsideRecognizer)
}
}
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
if(self.tapOutsideRecognizer != nil) {
self.view.window?.removeGestureRecognizer(self.tapOutsideRecognizer)
self.tapOutsideRecognizer = nil
}
}
func close(sender: AnyObject) {
self.dismissViewControllerAnimated(true, completion: nil)
}
func handleTapBehind(sender: UITapGestureRecognizer) {
if (sender.state == UIGestureRecognizerState.Ended) {
let location: CGPoint = sender.locationInView(nil)
if (!self.view.pointInside(self.view.convertPoint(location, fromView: self.view.window), withEvent: nil)) {
self.view.window?.removeGestureRecognizer(sender)
self.close(sender)
}
}
}
func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWithGestureRecognizer otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
}
Swift 3
class ModalParentViewController: UIViewController, UIGestureRecognizerDelegate {
private var tapOutsideRecognizer: UITapGestureRecognizer!
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
if(self.tapOutsideRecognizer == nil) {
self.tapOutsideRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.handleTapBehind))
self.tapOutsideRecognizer.numberOfTapsRequired = 1
self.tapOutsideRecognizer.cancelsTouchesInView = false
self.tapOutsideRecognizer.delegate = self
appDelegate.window?.addGestureRecognizer(self.tapOutsideRecognizer)
}
}
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
if(self.tapOutsideRecognizer != nil) {
appDelegate.window?.removeGestureRecognizer(self.tapOutsideRecognizer)
self.tapOutsideRecognizer = nil
}
}
func close(sender: AnyObject) {
self.dismiss(animated: true, completion: nil)
}
// MARK: - Gesture methods to dismiss this with tap outside
func handleTapBehind(sender: UITapGestureRecognizer) {
if (sender.state == UIGestureRecognizerState.ended) {
let location: CGPoint = sender.location(in: nil)
if (!self.view.point(inside: self.view.convert(location, from: self.view.window), with: nil)) {
self.view.window?.removeGestureRecognizer(sender)
self.close(sender: sender)
}
}
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
}
Ah ok. So I'm afraid thats not quite possible using the presentModalViewController: method. The whole idea of a "modal" view/window/message box/etc. pp. is that the user cannot do anything else than processing whatever the view/window/message box/etc. pp. wants him/her to do.
What you want to do instead is not present a modal view controller, but rather load and show your form view controller the regular way. Note in your master controller that the form is just showing e.g. with a BOOL variable and then handle there any taps that might occur. If your form is showing, dismiss it.
I use it in this form without any problems neither on iOS 7.1 nor iOS 8.3.
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[self.view.window addGestureRecognizer:self.tapBehindGesture];
}
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
[self.view.window removeGestureRecognizer:self.tapBehindGesture];
}
- (UITapGestureRecognizer*)tapBehindGesture
{
if (_tapBehindGesture == nil)
{
_tapBehindGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(tapBehindRecognized:)];
_tapBehindGesture.numberOfTapsRequired = 1;
_tapBehindGesture.cancelsTouchesInView = NO;
_tapBehindGesture.delegate = self;
}
return _tapBehindGesture;
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
return YES;
}
FYI, on iOS 13 form sheets now have a standard dismiss gesture - they go away when you touch anywhere inside the modal and swipe it down (set the new isModalInPresentation property to false if you want to prevent that).
I made a navigationController that auto dismiss for iPad
https://github.com/curciosobrinho/NavAutoDismiss
It is just the same code as above, working on iOS 8.
So, all the credits go to the people above.
I just did it to be more generic and easier to use.
You just need to copy BOTH files to your project
Import the header file (.h)
And use your viewcontroller (that you want to show) as the rootViewController.
How to use it:
//Import the .h file
#import "NavDismissViewController.h"
//Instanciate your view controller (the view you want to present)
YourViewController * yourVC = [YourViewController new];
//Instanciate the NavDismissViewController with your view controller as the rootViewController
NavDismissViewController *nav = [[NavDismissViewController alloc] initWithRootViewController:yourVC];
//if you want to change the navigationBar translucent behaviour
[nav.navigationBar setTranslucent:NO];
//Choose the Modal style
nav.modalPresentationStyle=UIModalPresentationFormSheet;
//present your controller
[self presentViewController:nav animated:YES completion:nil];
//Done
Based on some of the questions and with some improvements and updates:
- (void)tapOutsideDetected:(UITapGestureRecognizer *)sender {
if (#available(iOS 13, *)) {} else {
UIView *aView = self.navigationController ? self.navigationController.view : self.view;
CGPoint location = [sender locationInView: aView];
if (![aView pointInside:location withEvent:nil]) {
[self removeGestureRecognizer:sender];
[self dismissViewControllerAnimated:YES completion:nil];
}
}
}
Where:
- (void) removeGestureRecognizer:(UIGestureRecognizer *) gesture {
if (self.recognizer) {
UIView *aView = self.recognizer.view;
if (aView) {
[aView removeGestureRecognizer:self.recognizer];
}
}
}
In this case, the UITapGestureRecognizer has been applied to the UIViewController
I am calling a Singleton method that does not get called when I try doing this.
I get no errors or anything, just that I am unable to see the CCLOG message.
Under what reasons would a compiler not give you error and not allow you to call a method?
[[GameManager sharedGameManager] openSiteWithLinkType:kLinkTypeDeveloperSite];
The method is defined as follows:
-(void)openSiteWithLinkType:(LinkTypes)linkTypeToOpen
{
CCLOG(#"WE ARE IN openSiteWithLinkType"); //I NEVER SEE THIS MESSAGE
NSURL *urlToOpen = nil;
if (linkTypeToOpen == kLinkTypeDeveloperSite)
{
urlToOpen = [NSURL URLWithString:#"http://somesite.com"];
}
if (![[UIApplication sharedApplication]openURL:urlToOpen])
{
CCLOG(#"%#%#",#"Failed to open url:",[urlToOpen description]);
[self runSceneWithID:kMainMenuScene];
}
}
HERE IS THE CODE TO MY SINGLETON:
#import "GameManager.h"
#import "MainMenuScene.h"
#implementation GameManager
static GameManager* _sharedGameManager = nil;
#synthesize isMusicON;
#synthesize isSoundEffectsON;
#synthesize hasPlayerDied;
+(GameManager*) sharedGameManager
{
#synchronized([GameManager class])
{
if (!_sharedGameManager)
{
[[self alloc] init];
return _sharedGameManager;
}
return nil;
}
}
+(id)alloc
{
#synchronized ([GameManager class])
{
NSAssert(_sharedGameManager == nil,#"Attempted to allocate a second instance of the Game Manager singleton");
_sharedGameManager = [super alloc];
return _sharedGameManager;
}
return nil;
}
-(id)init
{
self = [super init];
if (self != nil)
{
//Game Manager initialized
CCLOG(#"Game Manager Singleton, init");
isMusicON = YES;
isSoundEffectsON = YES;
hasPlayerDied = NO;
currentScene = kNoSceneUninitialized;
}
return self;
}
-(void) runSceneWithID:(SceneTypes)sceneID
{
SceneTypes oldScene = currentScene;
currentScene = sceneID;
id sceneToRun = nil;
switch (sceneID)
{
case kMainMenuScene:
sceneToRun = [MainMenuScene node];
break;
default:
CCLOG(#"Unknown Scene ID, cannot switch scenes");
return;
break;
}
if (sceneToRun == nil)
{
//Revert back, since no new scene was found
currentScene = oldScene;
return;
}
//Menu Scenes have a value of < 100
if (sceneID < 100)
{
if (UI_USER_INTERFACE_IDIOM() != UIUserInterfaceIdiomPad)
{
CGSize screenSize = [CCDirector sharedDirector].winSizeInPixels;
if (screenSize.width == 960.0f)
{
//iPhone 4 retina
[sceneToRun setScaleX:0.9375f];
[sceneToRun setScaleY:0.8333f];
CCLOG(#"GM: Scaling for iPhone 4 (retina)");
}
else
{
[sceneToRun setScaleX:0.4688f];
[sceneToRun setScaleY:0.4166f];
CCLOG(#"GM: Scaling for iPhone 3G or older (non-retina)");
}
}
}
if ([[CCDirector sharedDirector] runningScene] == nil)
{
[[CCDirector sharedDirector] runWithScene:sceneToRun];
}
else
{
[[CCDirector sharedDirector] replaceScene:sceneToRun];
}
}
-(void)openSiteWithLinkType:(LinkTypes)linkTypeToOpen
{
CCLOG(#"WE ARE IN openSiteWithLinkType");
NSURL *urlToOpen = nil;
if (linkTypeToOpen == kLinkTypeDeveloperSite)
{
urlToOpen = [NSURL URLWithString:#"http://somesite.com"];
}
if (![[UIApplication sharedApplication]openURL:urlToOpen])
{
CCLOG(#"%#%#",#"Failed to open url:",[urlToOpen description]);
[self runSceneWithID:kMainMenuScene];
}
}
-(void) test
{
CCLOG(#"this is test");
}
#end
Perhaps sharedGameManager is returning nil? This won't cause an error.
Edit: The issue is in the new code you posted:
if (!_sharedGameManager) {
[[self alloc] init];
return _sharedGameManager;
}
return nil;
If _sharedGameManager is non-nil, this will return nil. You want:
if (!_sharedGameManager) {
[[self alloc] init];
}
return _sharedGameManager;
Check this Cocoa With Love article on how to make a proper singleton.
Please, could you give a simple complete example of Cocoa application with NSOutlineView with hierarchical data representation not so ambiguous like NSOutlineView and NSTreeController example
.
Thanks!
This example uses a simple two layer hierarchy of two parents Foo and Bar who each have two kids, Foox, Fooz and Barx, Barz respectively, the outline view is the default one with just two columns and the identifier of the second column is set to "children".
NSDictionary *firstParent = #{#"parent": #"Foo", #"children": #[#"Foox", #"Fooz"]};
NSDictionary *secondParent = #{#"parent": #"Bar", #"children": #[#"Barx", #"Barz"]};
NSArray *list = #[firstParent, secondParent];
- (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item
{
if ([item isKindOfClass:[NSDictionary class]]) {
return YES;
}else {
return NO;
}
}
- (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item
{
if (item == nil) { //item is nil when the outline view wants to inquire for root level items
return [list count];
}
if ([item isKindOfClass:[NSDictionary class]]) {
return [[item objectForKey:#"children"] count];
}
return 0;
}
- (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item
{
if (item == nil) { //item is nil when the outline view wants to inquire for root level items
return [list objectAtIndex:index];
}
if ([item isKindOfClass:[NSDictionary class]]) {
return [[item objectForKey:#"children"] objectAtIndex:index];
}
return nil;
}
- (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)theColumn byItem:(id)item
{
if ([[theColumn identifier] isEqualToString:#"children"]) {
if ([item isKindOfClass:[NSDictionary class]]) {
return [NSString stringWithFormat:#"%i kids",[[item objectForKey:#"children"] count]];
}
return item;
}else{
if ([item isKindOfClass:[NSDictionary class]]) {
return [item objectForKey:#"parent"];
}
}
return nil;
}
NSOutlineView using NSURL and Swift simple example populating directory structure
AppDelegate.swift
import Cocoa
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
var mainWindowController: MainWindowController?
func applicationDidFinishLaunching(aNotification: NSNotification) {
// Insert code here to initialize your application
let mainWindowController = MainWindowController()
mainWindowController.showWindow(self)
self.mainWindowController = mainWindowController
}
func applicationWillTerminate(aNotification: NSNotification) {
// Insert code here to tear down your application
}
func applicationShouldTerminateAfterLastWindowClosed(sender: NSApplication) -> Bool {
return true
}
}
FileItem.swift
class FileItem {
var url: NSURL!
var parent: FileItem?
var isLeaf:Bool = false
static let fileManager = NSFileManager.defaultManager()
static let requiredAttributes = [NSURLIsDirectoryKey]
static let options: NSDirectoryEnumerationOptions = [.SkipsHiddenFiles, .SkipsPackageDescendants, .SkipsSubdirectoryDescendants]
lazy var children: [FileItem]? = {
if let enumerator = fileManager.enumeratorAtURL(self.url, includingPropertiesForKeys:FileItem.requiredAttributes, options: FileItem.options, errorHandler: nil) {
var files = [FileItem]()
while let localURL = enumerator.nextObject() as? NSURL {
do {
let properties = try localURL.resourceValuesForKeys(FileItem.requiredAttributes)
// check this
files.append(FileItem(url: localURL, parent: self, isLeaf: (properties[NSURLIsDirectoryKey] as! NSNumber).boolValue))
} catch {
print("Error reading file attributes")
}
}
return files
}
return nil
}()
init(url: NSURL, parent: FileItem?, isLeaf: Bool){
self.url = url
self.parent = parent
self.isLeaf = isLeaf
}
var displayName: String {
get {
return self.url.lastPathComponent!
}
}
var count: Int {
return (self.children?.count)!
}
func childAtIndex(n: Int) -> FileItem? {
return self.children![n]
}
}
I'm using MainWindowController with xib deleted provided window from app delegate and the IBOutlet for window
import Cocoa
class MainWindowController: NSWindowController {
#IBOutlet weak var outline: NSOutlineView!
private var rootItem: FileItem? = FileItem(url: NSURL(fileURLWithPath:"/"), parent: nil, isLeaf: true)
override func windowDidLoad() {
super.windowDidLoad()
// Implement this method to handle any initialization after your window controller's window has been loaded from its nib file.
}
override var windowNibName: String? {
return "MainWindowController"
}
}
//MARK: - NSOutlineViewDelegate
extension MainWindowController: NSOutlineViewDelegate {
func outlineView(outlineView: NSOutlineView, viewForTableColumn tableColumn: NSTableColumn?, item: AnyObject) -> NSView? {
let view = outline.makeViewWithIdentifier("TextCell", owner: self) as? NSTableCellView
if let it = item as? FileItem {
if let textField = view?.textField {
textField.stringValue = it.displayName
}
} else {
if let textField = view?.textField {
textField.stringValue = self.rootItem!.displayName
}
}
return view
}
}
//MARK: - NSOutlineViewDataSource
extension MainWindowController: NSOutlineViewDataSource {
func outlineView(outlineView: NSOutlineView, child index: Int, ofItem item: AnyObject?) -> AnyObject {
if let it = item as? FileItem {
return it.childAtIndex(index)!
}
return rootItem!
}
func outlineView(outlineView: NSOutlineView, numberOfChildrenOfItem item: AnyObject?) -> Int {
if let it = item as? FileItem {
return it.count
}
return 1
}
func outlineView(outlineView: NSOutlineView, isItemExpandable item: AnyObject) -> Bool {
if let it = item as? FileItem {
if it.count > 0 {
return true
}
}
return false
}
}
Example here OutlineView
It's a modified version of this example NSOutlineViewInSwift