NSOutlineView example - objective-c

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

Related

Objective C to Swift dispatch process

i'm having some trouble trying to convert the following code in Objective C to Swift:
- (BOOL)loadEventsAtDate:(NSDate*)date
{
dispatch_async(self.bgQueue, ^{
[self bg_loadOneDay];
});
return YES;
}
- (void)bg_loadOneDay
{
__block NSDate *date;
dispatch_sync(dispatch_get_main_queue(), ^{
.....
});
if (date) {
[self bg_loadEventsAtDate:date];
}
}
- (void)bg_loadEventsAtDate:(NSDate*)date
{
dispatch_async(dispatch_get_main_queue(), ^{
......
});
}
Can you please help me?I' m quite new to this!!!Thank you in advance!!!
Try this:
func loadEventsAtDate(date: NSDate) -> Bool {
let bgQueue = dispatch_queue_create("MY_BG_QUEUE", DISPATCH_QUEUE_SERIAL)//use your `bgQueue`
dispatch_async(bgQueue) {
self.bg_loadOneDay()
}
return true
}
func bg_loadOneDay() {
let date = NSDate()
dispatch_async(dispatch_get_main_queue()) {
//....
}
bg_loadEventsAtDate(date)//not sure which date you are sending here, please check
}
func bg_loadEventsAtDate(date: NSDate) {
dispatch_async(dispatch_get_main_queue()) {
//....
}
}

Annotations not loading properly

Im trying to load my annotations using JPSThumbnail on swift. With the code below for some reason whenever It loads, it loads my annotations with the usual red pin. I have no clue in objective C so I dont know what should be done? What is missing in my code, am I appending right to the array?
JPSThumnail Github
JPSThumbnailAnnotation:
+ (instancetype)annotationWithThumbnail:(JPSThumbnail *)thumbnail {
return [[self alloc] initWithThumbnail:thumbnail];
}
- (id)initWithThumbnail:(JPSThumbnail *)thumbnail {
self = [super init];
if (self) {
_coordinate = thumbnail.coordinate;
_thumbnail = thumbnail;
}
return self;
}
- (MKAnnotationView *)annotationViewInMap:(MKMapView *)mapView {
if (!self.view) {
self.view = (JPSThumbnailAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:kJPSThumbnailAnnotationViewReuseID];
if (!self.view) self.view = [[JPSThumbnailAnnotationView alloc] initWithAnnotation:self];
} else {
self.view.annotation = self;
}
[self updateThumbnail:self.thumbnail animated:NO];
return self.view;
}
- (void)updateThumbnail:(JPSThumbnail *)thumbnail animated:(BOOL)animated {
if (animated) {
[UIView animateWithDuration:0.33f animations:^{
_coordinate = thumbnail.coordinate; // use ivar to avoid triggering setter
}];
} else {
_coordinate = thumbnail.coordinate; // use ivar to avoid triggering setter
}
[self.view updateWithThumbnail:thumbnail];
}
My Swift Code:
func annotations() -> [JPSThumbnailAnnotation] {
var annotations: [JPSThumbnailAnnotation] = []
let pointData = NSData(contentsOfFile: NSBundle.mainBundle().pathForResource("1000", ofType: "geojson")!)
let points = NSJSONSerialization.JSONObjectWithData(pointData!,
options: nil,
error: nil) as! NSDictionary
for point in points["glimps"] as! NSArray {
var a = JPSThumbnail()
a.image = UIImage(named: "empire.jpg")
a.title = "Empire State Building"
a.subtitle = "NYC Landmark"
var lat = (point as! NSDictionary)["latitude"] as! CLLocationDegrees
var lon = (point as! NSDictionary)["longitude"] as! CLLocationDegrees
a.coordinate = CLLocationCoordinate2DMake(lat, lon)
a.disclosureBlock = { println("selected Empire") }
var a1: JPSThumbnailAnnotation = JPSThumbnailAnnotation(thumbnail: a)
annotations.append(a1)
}
return annotations
}
func mapView(mapView: MKMapView!, viewForAnnotation annotation: MKAnnotation!) -> MKAnnotationView! {
return (annotation as? JPSThumbnailAnnotationProtocol)?.annotationViewInMap(mapView)
}

UILabel doesn't show inputView

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()
}
}

Dismiss modal view form sheet controller on outside tap

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

Singleton method not getting called in Cocos2d

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.