Gesture controls crash when calling methods on C4WorkSpace.m - gesture

I'm working with the C4 framework on a project and I'm having a bit of trouble with using the gesture controls and calling methods from C4WorkSpace.
I found this link that had to do with this problem but it didn't seem to stop my program from throwing an error.
A little background: I'm creating a custom button class that has 4 properties: UIColor, C4Shape and 2 floats (x/y position). I'm storing the custom button objects in an array and have this bit of code going to add a gesture to each button and add it to the canvas.
C4WorkSpace.m
for (button in buttonArray){
[button.shape addGesture:TAP name:#"tapGesture" action:#"tapped"];
[self.canvas addShape:button.shape];
[self listenFor:#"tapped" fromObject:button andRunMethod:#"doThis:"];
}
And I also have a function outside my '-(void)setup' that just prints a log message:
-(void)doThis:(NSNotification *)notification{
NSLog(#"notification test");
}
I have extended my C4Shape class with a category and when I call this method it works fine:
#import "C4Shape+myC4Shape.h"
#implementation C4Shape (myC4Shape)
-(void)printTest{
NSLog(#"this is a print test");
}
#end
The error I'm getting gets thrown when I try and tap on a button, it says: Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[C4Shape tapped]: unrecognized selector sent to instance 0x931e9c0'
I have my "action" set to 'tapped', but that's just something I made up. I know this usually calls a method inside the shape class, but I'm trying to call a method that isn't in the C4Shape class but on the main WorkSpace. I'm not sure what's causing this or what I'm doing wrong? Is there something specific I need to be writing in the 'action' part of the method?

this Obj-C stuff can be confusing at first. Hopefully this will help.
I notice two things for you to change.
The first is in regards to your TAP gesture. When you add this to your code you're actually telling the gesture to recognize a TAP and then run the tapped method. However, this method doesn't exist (hence the -[C4Shape tapped]: unrecognized selector sent to instance 0x931e9c0). You should have a method in your code called -(void)tapped{}; for this message to go away.
The following code illustrates this:
#implementation C4WorkSpace
-(void)setup {
[self addGesture:TAP name:#"tap" action:#"tapped"];
}
-(void)tapped {
C4Log(#"tapped!");
}
#end
Putting that into a shape is quite similar.
Second, for the subclassing, C4Shape (and all other visual objects) inherit from C4Control so I would suggest that subclassing C4Control isn't the best approach. For creating your own custom button with a C4Shape I would subclass C4Shape directly.
The above code would look exactly the same for a subclassed C4Shape except that if you created a MyShape subclass would have #implementation MyShape instead.
Finally, you want to add a little broadcast message to your tap method so that the canvas can listen for it... All in all your .h and .m files for your subclass should look like this:
.h
#import "C4Shape.h"
#interface MyShape : C4Shape
#end
.m
#import "MyShape.h"
#implementation MyShape
-(void)setup {
[self addGesture:TAP name:#"tap" action:#"tapped"];
}
-(void)tapped {
[self postNotification:#"tapNotification"];
}
#end
Finally, with those two files in your project you can add the following to your workspace:
#import "C4WorkSpace.h"
#import "MyShape.h"
#implementation C4WorkSpace
-(void)setup {
MyShape *m = [[MyShape alloc] init];
[m rect:CGRectMake(0, 0, 100, 100)];
m.center = self.canvas.center;
[self.canvas addShape:m];
[self listenFor:#"tapNotification" fromObject:m andRunMethod:#"heardTap:"];
}
-(void)heardTap:(NSNotification *)aNotification {
MyShape *notificationShape = (MyShape *)[aNotification object];
C4Log(#"%4.2f,%4.2f",notificationShape.center.x,notificationShape.center.y);
C4Log(#"%#",notificationShape.strokeColor);
}
#end
Here's a link to a gist with the 3 files you'll need for this to run.
Button Subclass with Canvas Listener
PS thanks for giving C4 a try!
Oh, and this is the output I get in Xcode's console when I tap on the square:
[C4Log] 384.00,512.00
[C4Log] UIDeviceRGBColorSpace 1 0.1 0.1 1
This gives me the x and y coordinates, as well as the strokeColor of the "button" without having to add these as objects to the MyShape class (because they already exist in C4Shape).

Related

NSViewController not dealloc'd when outlets are bound

I have the following files:
Main.storyboard
This is the same as the default storyboard created when creating a new project with a few additions: There is a button in the view which is connected to the button outlet of the view controller (instance of ViewController) and the doTheThing: action on the view controller.
ViewController.m
#import "ViewController.h"
#interface ViewController ()
#property (weak) IBOutlet NSButton *button;
#property (weak) NSViewController *controller;
#end
#implementation ViewController
- (IBAction)doTheThing:(id)sender {
if (self.controller) {
NSLog(#"Removing %#", self.controller);
[self.controller.view removeFromSuperview];
[self.controller removeFromParentViewController];
} else {
self.controller = [[NSStoryboard storyboardWithName:#"Another" bundle:[NSBundle mainBundle]] instantiateInitialController];
[self addChildViewController:self.controller];
[self.view addSubview:self.controller.view];
NSLog(#"Adding %#", self.controller);
}
}
#end
Another.storyboard
A simple storyboard containing a single scene (view controller + view) that is set to the initial controller and is an instance of AnotherViewController. There is a label in the view that is not connected to any outlet.
AnotherViewController.m
#import "AnotherViewController.h"
#interface AnotherViewController ()
#property (weak) IBOutlet NSTextField *label;
#end
#implementation AnotherViewController
- (void)dealloc {
NSLog(#"Deallocing AnotherViewController %#", self);
}
#end
When I run the app and click the button, it adds the view controller and view from Another.storyboard, and when I click the button again they are removed and the instance of AnotherViewController is deallocated.
However, if I connect the label in Another.storyboard to the label outlet on the AnotherViewController, the deallocation never occurs. Why is this and what can I do to fix it?
Edit: I do have a few workarounds, but they aren't very desirable and I would prefer to understand why the recommended way (storyboards and outlets) isn't working properly.
Undesirable workaround 1: Do the same thing but load the view controller and view from a XIB file. This works as expected, but ideally I would be able to do the same thing using storyboards.
Undesirable workaround 2: Bind all my outlets manually in code in the view controller's viewDidLoad method. This is just tedious and ugly as it requires iterating through all the view's subviews and comparing identifiers.
Apple have confirmed (via bug report) that this is a known issue and will be fixed in OS X 10.10.3.

How to cancel out of QColorDialog::getColor()?

In a QT application I am working on, we let the user pick a color using QColorDialog::getColor(). Based on an external event, I need to cancel this opened dialog. Is there a way to do it? I didn't see any other static method on QColorDialog to exit out of the dialog.
Or, may be a better method would be to close all opened dialogs. Is there such a method?
Following this Qt Forum post,
http://www.qtforum.org/article/37032/ok-cancel-buttons-on-qcolordialog.html
I tested the following code.
QColor color = QColorDialog::getColor();
if (!color.isValid()) return;
// Your process for selected color
// ...
and it properly worked for me.
Here is the code which you call by QColorDialog::getColor:
QColorDialog dlg(parent);
if (!title.isEmpty())
dlg.setWindowTitle(title);
dlg.setOptions(options);
dlg.setCurrentColor(initial);
dlg.exec();
return dlg.selectedColor();
As you can see it creates an instance of QColorDialog of stack, sets its initial properties, shows it and returns the result. You can use the same code to create the dialog BUT pay attention on how the dialog is shown.
Method QDialog::exec creates a new event loop (http://qt-project.org/doc/qt-4.8/qeventloop.html) and don't return until the dialog is closed.
That's why you can't call any method of QDialog. Thus QDialog::exec creates so called modal window (http://qt-project.org/doc/qt-4.8/qwidget.html#windowModality-prop).
Solution
To be able to interact with the dialog you need to create it using operator new and use method QDialog::show to show the dialog. But this method returns control immediately when the dialog is shown. So you won't be able to get the color in the next line of your code. Instead you need to subscribe to the dialog signals accepted and rejected, process the results (dialog->currentColor()) and delete the dialog object.
Also you've asked about a way to close all opened dialog. Supposing that all you dialogs are inherited from QDialog:
foreach (QWidget *widget, QApplication::topLevelWidgets()) {
if (QDialog* dialog = qobject_cast<QDialog*>(widget))
dialog->close();
}
This works for me:
QColorDialog *dialog = new QColorDialog(this);
dialog->show();
QObject::connect(dialog,&QDialog::accepted,[=](){
QColor color = dialog->currentColor();
QVariant variant = color;
QString rgb= variant.toString();
ui->eg->setStyleSheet("QLabel { color :"+rgb+" ; }");});`
I hope It helps someone! The above works to change the QLabel font and/or frame but u can try different stylesheets i.e
ui->label->setStyleSheet("QLabel { background-color :"+rgb+" ; color : white; }");
You can not do this when using the static getColor() function.
Construct a dialog object instead so you get a pointer allowing you to call all available functions (like reject() or close()).

how to use buttons defined in one class that class inherits UINavigationController and appeared in every other class in objective c?

is it possible that i define a class which inherit UINavigationController and add buttons in this class.and when ever i use this class in another class buttons will be shown on navigation bar of each class..hope it is clear.Thanks in Advance
Instead of using UINavigationController, I would suggest you to subclass UIViewController and configure what ever you want in this class.
Further where ever you need the same implementation, just use your newly created class.
For eg; create new CustomViewController as below
in .h file
#interface CustomViewController : UIViewController {
}
in .m file,
-(void)viewDidLoad {
[super viewDidLoad];
// Custom Implementation of bar and View as per your requirement
}
Now if you want to have this configuration in some other View Controller, subclass CustomViewController and everything will be done. Eg: If you have ListViewController,
in .h file,
#interface ListViewController : CustomViewController {
}
in .m file
-(void)viewDidLoad {
[super viewDidLoad]; // Its super class is "CustomViewController" so viewDidLoad method you implemented in CustomViewController will be called and your view will be configured.
// Further Whatever you want in this controller
}
Hope it helps.

How to pass an NSString from modal view to parent view

I have a parent view and a modal view with a text box. What I am trying to do is pass whatever is entered into the text box from the modal view and then pass it to a label in the parent view which updates the label to what was entered. I hope that made any sense.
I have been pulling my hair out for a couple of weeks trying to figure this out with no luck. I found many examples and tutorials about segues and passing between views that are being pushed but nothing about modal views and passing back to the parent view.
I have been trying to understand this and need a good example. I kind of understand the prepare for segue concept but for some reason, I just can't figure this one out. Any help on this would be much appreciated and you would be my hero for life lol.
In my project that uses segues, here's how I did it (note that I'm new to iOS, so there's probably "better" ways, and this may be obvious to the iOS veterans):
The short version: define a callback protocol in your modal view controller's .h file. When your modal view controller closes, it checks to see if the presenter implements that protocol and invokes those methods to pass along the data.
So like you said, let's say your modal view controller just gathers a single string value from the user and then they click OK or Cancel. That class might look like this:
#interface MyModalViewController : UIViewController
...
#end
I'm suggesting you add a protocol like this to the same header:
#protocol MyModalViewControllerCallback
-(void) userCancelledMyModalViewController:(MyModalViewController*)vc;
-(void) userAcceptedMyModalViewController:(MyModalViewController*)vc
withInput:(NSString*)s;
#end
Then in MyModalViewController.m, you add a viewDidDisappear with code similar to this:
-(void) viewDidDisappear:(BOOL)animated {
UIViewController* presenter = self.presentingViewController;
// If the presenter is a UINavigationController then we assume that we're
// notifying whichever UIViewController is on the top of the stack.
if ([presenter isKindOfClass:[UINavigationController class]]) {
presenter = [(UINavigationController*)presenter topViewController];
}
if ([presenter conformsToProtocol:#protocol(MyModalViewControllerCallback)]) {
// Assumes the presence of an "accepted" ivar that knows whether they
// accepted or cancelled, and a "data" ivar that has the data that the
// user entered.
if (accepted) {
[presenter userAcceptedMyModalViewController:self withInput:data];
}
else {
[presenter userCancelledMyModalViewController:self];
}
}
[super viewDidDisappear:animated];
}
And finally in the parent view, you implement the new #protocol, e.g. in the .h:
#interface MyParentViewController : UIViewController <MyModalViewControllerCallback>
...
#end
and in the .m:
#implementation MyParentViewController
...
-(void) userCancelledMyModalViewController:(MyModalViewController*)vc {
// Update the text field with something like "They clicked cancel!"
}
-(void) userAcceptedMyModalViewController:(MyModalViewController*)vc
withInput:(NSString*)s {
// Update the text field with the value in s
}
...
#end

UINavigationController and UINavigationBarDelegate.ShouldPopItem() with MonoTouch

How do I pop up an UIAlertView when the back button of a UINavigationBar (controlled by a UINavigationController) was tapped? Under certain conditions, I want to ask the user an "Are you sure?" type of question so he could either abort the action and stay on the current view or pop the navigation stack and go to the parent view.
The most appealing approach I found was to override ShouldPopItem() on UINavigationBar's Delegate.
Now, there is a quite similar question here: iphone navigationController : wait for uialertview response before to quit the current view
There are also a few other questions of similar nature, for example here:
Checking if a UIViewController is about to get Popped from a navigation stack?
and How to tell when back button is pressed in a UINavigationControllerStack
All of these state "subclass UINavigationController" as possible answers.
Then there is this one that reads like subclassing UINavigationController is generally not a good idea:
Monotouch: UINavigationController, override initWithRootViewController
The apple docs also say that UINavigationController is not intended to be subclassed.
A few others state that overriding ShouldPopItem() is not even possible when using a UINavigationController as that does not allow to assign a custom/subclassed UINavigationBarDelegate to the UINavigationBar.
None of my attempts of subclassing worked, my custom Delegate was not accepted.
I also read somewhere that it might be possible to implement ShouldPopItem() within my custom UINavigationController since it assigns itself as Delegate of its UINavigationBar.
Not much of a surprise, this didn't work. How would a subclass of UINavigationController know of the Methods belonging to UINavigationBarDelegate. It was rejected: "no suitable method found to override". Removing the "override" keyword compiled, but the method is ignored completely (as expected). I think, with Obj-C one could implement several Protocols (similar to Interfaces in C# AFAIK) to achieve that. Unfortunately, UINavigationBarDelegate is not an Interface but a Class in MonoTouch, so that seems impossible.
I'm pretty much lost here. How to override ShouldPopItem() on UINavigationBar's Delegate when it is controlled by a UINavigationController? Or is there any other way to pop up an UIAlertView and wait for it's result before possibly popping the navigation stack?
This post is a bit old, but in case you're still interested in a solution (still involves subclassing though):
This implements a "Are you sure you want to Quit?" alert when the back button is pressed, modified from the code here: http://www.hanspinckaers.com/custom-action-on-back-button-uinavigationcontroller/
Turns out if you implement the UINavigationBarDelegate in the CustomNavigationController, you can make use of the shouldPopItem method:
CustomNavigationController.h :
#import <Foundation/Foundation.h>
#interface CustomNavigationController : UINavigationController <UIAlertViewDelegate, UINavigationBarDelegate> {
BOOL alertViewClicked;
BOOL regularPop;
}
#end
CustomNavigationController.m :
#import "CustomNavigationController.h"
#import "SettingsTableController.h"
#implementation CustomNavigationController
- (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item {
if (regularPop) {
regularPop = FALSE;
return YES;
}
if (alertViewClicked) {
alertViewClicked = FALSE;
return YES;
}
if ([self.topViewController isMemberOfClass:[SettingsTableViewController class]]) {
UIAlertView * exitAlert = [[[UIAlertView alloc] initWithTitle:#"Are you sure you want to quit?" message:nil delegate:self cancelButtonTitle:#"Cancel" otherButtonTitles:#"Yes", nil] autorelease];
[exitAlert show];
return NO;
}
else {
regularPop = TRUE;
[self popViewControllerAnimated:YES];
return NO;
}
}
-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
if (buttonIndex == 0) {
//Cancel button
}
else if (buttonIndex == 1) {
//Yes button
alertViewClicked = TRUE;
[self popViewControllerAnimated:YES];
}
}
#end
The weird logic with the "regularPop" bool is because for some reason just returning "YES" on shouldPopItem only pops the navbar, not the view associated with the navBar - for that to happen you have to directly call popViewControllerAnimated (which then calls shouldPopItem as part of its logic.)
For reference, the route I took after giving up on ShouldPopItem() is to replace the back button with a UIBarButtonItem that has a custom UIButton assigned as it's CustomView. The UIButton is crafted to look like the original back button using two images for the normal and the pressed state. Finally, hiding the original back button is required.
Way too much code for what it's supposed to do. So yeah, thanks Apple.
BTW: Another possibility is creating a UIButton with the secret UIButtonType 101 (which is actually the back button) but I avoided this as it may break at any later iOS version.
Override only UINavigationBarDelegate methods in a UINavigationController subclass and it should simply work. Be cautious that the protocol methods are also called when you push or pop a view controller from inside your code and not only when the back button is pressed. This is because them are push/pop notifications not button pressed actions.
Xamarin does provide the IUINavigationBarDelegate interface to allow you to implement the UINavigationBarDelegate as part of your custom UINavigationController class.
The interface however does not require that the ShouldPopItem method be implemented. All the interface does is add the appropriate Protocol attribute to the class so it can be used as a UINavigationBarDelegate.
So in addition you need to add the ShouldPopItem declaration to the class as follows:
[Export ("navigationBar:shouldPopItem:")]
public bool ShouldPopItem (UINavigationBar navigationBar, UINavigationItem item)
{
}
I've merged this solution with a native Obj-C solution. This is the way I'm currently handling the cancellation of the BACK button in iOS
It seems that it is possible to handle the shouldPopItem method of the NavigationBar in this way:
Subclass a UINavigationController
Mark your custom UINavigationController with the IUINavigationBarDelegate
Add this method with the Export attribute
[Export ("navigationBar:shouldPopItem:")]
public bool ShouldPopItem (UINavigationBar navigationBar, UINavigationItem item)
{
}
Now you can handle popping in the ShoulPopItem method. An example to this is to create an interface like this
public interface INavigationBackButton
{
// This method should return TRUE to cancel the "back operation" or "FALSE" to allow normal back
bool BackButtonPressed();
}
Then mark your UIViewController which needs to handle the back button with this interface. Implement something like this
public bool BackButtonPressed()
{
bool needToCancel = // Put your logic here. Remember to return true to CANCEL the back operation (like in Android)
return needToCancel;
}
Then in your ShouldPopItem Implementation have something like this
tanks to: https://github.com/onegray/UIViewController-BackButtonHandler/blob/master/UIViewController%2BBackButtonHandler.m
[Export("navigationBar:shouldPopItem:")]
public bool ShouldPopItem(UINavigationBar navigationBar, UINavigationItem item)
{
if (this.ViewControllers.Length < this.NavigationBar.Items.Length)
return true;
bool shouldPop = true;
UIViewController controller = this.TopViewController;
if (controller is INavigationBackButton)
shouldPop = !((INavigationBackButton)controller).BackButtonPressed();
if (shouldPop)
{
//MonoTouch.CoreFoundation.DispatchQueue.DispatchAsync
CoreFoundation.DispatchQueue.MainQueue.DispatchAsync(
() =>
{
PopViewController(true);
});
}
else
{
// Workaround for iOS7.1. Thanks to #boliva - http://stackoverflow.com/posts/comments/34452906
foreach (UIView subview in this.NavigationBar.Subviews)
{
if(subview.Alpha < 1f)
UIView.Animate(.25f, () => subview.Alpha = 1);
}
}
return false;
}

Resources