Core Plot: Cursor does not adjust over CPTGraphHostingView - objective-c

I use Core Plot in two macOS apps (first one is old, second one is new). Each of them has a CPTGraphHostingView where some plots are displayed. I can click, hold and drag to change the visible area.
In the first app, when I hoover over the plot, the cursor changes to an open hand. It also changes to a closed hand when I drag. This is what I want, and this is the behaviour in all the sample apps of Core Plot.
In the second app, the cursor always stays the same (arrow).
Edit: Here is a screenshot of the view hierarchy in Xcode (views behind the plot view are hidden). There is no view in front of the CPTGraphHostingView, just a few controls are higher in the view hierarchy, but they are positioned around the plot.
I was not able to find any difference between my two apps (or the second app and the example apps) that could cause this. Both apps compile with Xcode 10.1 and Core Plot release-2.3 branch.
What should I look for?

Check that there aren't any other invisible views obscuring the Core Plot hosting view. Does everything else in the second app (layout, appearance, etc.) work the way you expect?

Since initWithFrame of CPTGraphHostingView does not get called in my second app, the hand cursers are always nil and cannot be adjusted. This is from Apple's docs for "Creating a Custom View":
View instances that are created in Interface Builder don't call
initWithFrame: when their nib files are loaded, which often causes
confusion. Remember that Interface Builder archives an object when it
saves a nib file, so the view instance will already have been created
and initWithFrame: will already have been called.
The awakeFromNib method provides an opportunity to provide
initialization of a view when it is created as a result of a nib file
being loaded. When a nib file that contains a view object is loaded,
each view instance receives an awakeFromNib message when all the
objects have been unarchived. This provides the object an opportunity
to initialize any attributes that are not archived with the object in
Interface Builder.
So I subclassed CPTGraphHostingView and loaded the cursors from NSCursor in awakeFromNib, using the advice from Eric Skroch:
MyGraphHostingView.h:
#import <CorePlot/CorePlot.h>
NS_ASSUME_NONNULL_BEGIN
#interface MyGraphHostingView : CPTGraphHostingView
#end
NS_ASSUME_NONNULL_END
MyGraphHostingView.m:
#import "MyGraphHostingView.h"
#implementation MyGraphHostingView
-(void)awakeFromNib
{
[super awakeFromNib];
if (!self.closedHandCursor) {
self.closedHandCursor = [NSCursor closedHandCursor];
}
if (!self.openHandCursor) {
self.openHandCursor = [NSCursor openHandCursor];
}
self.allowPinchScaling = YES;
}
#end

Related

What's the advantage of using UIViewController as owner of xib?

Most of the time, owners of xib is a UIViewController.
I sort of use it my self.
Still I am confused why.
I suppose, the viewDidLoad and viewWillAppear is kind of the main selling point.
Is that it?
What are the advantage of using UIViewController as owners of an XIB?
A UIViewController object is the main way for views to appear within an iOS window.
Apple provides this as a fundamental, foundational building block (along with so many others) which you can use to build upon quickly and get your app out to market.
And when you subclass UIViewController, you're able to do lots of beautiful customizations which can be collected and eventually turned into (hopefully decent) products. When you subclass a UIViewController, you need to set the "owner" of a XIB file to that subclassed view controller (e.g. ThioViewController), so that way the app knows what object (and user interface) is being instantiated.
Hopefully this isn't too super abstract of an explanation.
First, spend a bit time to understand MVC http://en.wikipedia.org/wiki/Model–view–controller
This is the milestone of Objective-C (not only) development.
UIViewController is controller for all your views (inside this viewController). It provide starting point for you to create views on the screen, manipulate the views, handle actions from views etc.
You can create UIViewController programmatically.
XIB is representation of the screen which you can comfortably operate in Interface Builder to create and customize design of your application screen or one of the screens.
Since XIB represent the screen(view) it must be the controller which controls all the view on the screen - UIViewController or UINavigationController or other type of controller depending of your needs.
Most of time you will subclass UIViewController and use it to achieve you goals.
UIViewController have several subclasses which inherit directly from it (UINavigationController, UITabBarController).
Also UIViewController hav several methods (some of them)
-(void)viewDidLoad
This method is called after the view controller has loaded its view hierarchy into memory. This method is called regardless of whether the view hierarchy was loaded from a nib file or created programmatically in the loadView method. You usually override this method to perform additional initialization on views that were loaded from nib files.
and
-(void)viewWillAppear:(BOOL)animated
Parameters
animated
If YES, the view is being added to the window using an animation.
Discussion
This method is called before the receiver’s view is about to be added to a view hierarchy and before any animations are configured for showing the view. You can override this method to perform custom tasks associated with displaying the view. For example, you might use this method to change the orientation or style of the status bar to coordinate with the orientation or style of the view being presented. If you override this method, you must call super at some point in your implementation.
Please check Apple documentation for more information
https://developer.apple.com/LIBRARY/IOS/documentation/UIKit/Reference/UIViewController_Class/Reference/Reference.html

subviews with UIButtons - how to connect the buttons to IBActions

I am trying to create a simple app which uses a main view and has a smaller subview within it. I need to have buttons in the subview and I am having trouble getting the connections for the buttons to work. I have done the following:
Create a new View-based project, which gave me the ViewController.h & .m, the MainStoryboard.storyboard and the AppDelegate.h & .m.
Create the subView using NewFile - Objective-C Class - then naming it "subView1" and making it a subclass of UIViewController and checking the with xib check box.
This gave me the subView1.h &.m files and the subView1.xib.
I then re-sized the subView in the xib, by setting it's size to "FreeForm" in the attributes inspector and then specifying the width (to 280) and height (to 300) in the size inspector. I also change the background colour to differentiate it from the main view.
I then dragged a UIButton into the subView and connected it as an IBAction (which i named "clickButton1") to the subView1.h file using touchupinside.
For testing purposes only i then used a simple NSLog to check the functionality of the button which i placed in the subView.m file as follows:
-(IBAction)clickButton1:(id)sender {
NSLog(#"It Worked!");
}
In my ViewController.m file in the viewDidLoad i then added the following code to add the subview to the main view:
subView1 *sv1 = [[subView1 alloc]init];
sv1.view.frame = CGRectMake(20,120,280,300);
[self.view addSubview: sv1.view];
This all worked fine, and when i run the app i get the main view and the subview appear as expected. The problem is when i click on the button which is located in the subview it crashes with the following error:
Thread1:EXC_BAD_ACCESS(code=1, address=0xe00000008)
From what i have read i believe this may have something to do with how I am adding the subview and the fact that I am using ARC. Something about once the subview is added it is automatically released and therefore all buttons/connections etc within the subview are lost.
So my two questions are 1) Am i missing something silly here and is there an easy fix? and 2) Is this an appropriate way to create an app which uses subviews with buttons within them or is there a better way? Thanks to anyone who takes the time to answer!
I tried to reiterate what you said.
So you first made a View-Based Application (checking the "Use Storyboards" checkbox), and you put a button in the storyboard. Then, you control-dragged the button to the "ViewController.h" file. If that is right, you should just be able to put
NSLog(#"It Worked!");
in the method implementation (at least, I could do that)

Create a standalone view in storyboard to use programmatically

I'm trying to create and design a UIView using a storyboard but include it in a UIActionSheet programmatically. This is basically to avoid using CoreGraphics positioning functions with pixels
In old xcode I remember that it was possible to drag a UIView onto the nib without any controllers.
The view obviously has to be connected to the class, so it would have an IBOutlet, but not being added to the self.view
One thing that makes me feel like this should be possible is that if you drag a UIView into the controller black bar in storyboard it pops into place like so:
But its not shown on the screen itself. What is this feature for? Can I somehow open up this view and design it a bit?
Just create a new .xib file.
Right Click somewhere in the Navigator Area and select 'New File...'.
Select 'User Interface' from the list on the right and Select 'View'.
Click 'Next', 'Next', give your new view a name, and 'Create'.
A new .xib fill will be added to your project.
Double clicking on the new .xib file and it opens in Interface Builder (not Storyboard).
Edit/Design your View to your liking.
Then after you have your new view (.xib) in Interface Builder, it's a simple matter of creating a new subclass of UIView (ex. MyView), switching the class of your new view (.xib) to MyView, creating an instance of MyView in your controller, and adding it as a subview to your other view.
*And to answer your question about that little black bar at the bottom, it's called the 'Dock', and it's just a mini representation of the top-level documents of your scene. The dock is convenient for quickly dragging/dropping icons onto and making connections. See apple's storyboard description here. Ray Wenderlich has an easy to follow tutorial on storyboards here.
You cannot have a UIView outside of a UIViewController on a storyboard. I'm guessing it's because the storyboard would have no idea how to identify or instantiate with the current API. It is something I've had a use for myself. The solution is just use a XIB for the one UIView and load it up programmatically (just like used to do). I've found using a storyboard for most items and couple XIBs for re-usable views across multiple view controllers do work nicely together.
Here is some code I use to load a XIB as part of a custom object with the object gets initialized.
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
// Initialization code
[[[NSBundle mainBundle] loadNibNamed:#"BannerView" owner:self options:nil] objectAtIndex:0];
[self addSubview:self.view];
self.frame = self.view.frame;
}
return self;
}
As for dragging views down into the black bar on the storyboards. Those views are still part of the UIViewController, but they aren't a `subview' of the top level view. I think the document outline shows the hierarchy nicely.
The following view has 2.1.1 View, 2.1.2 View, etc outside of my main view hierarchy because they aren't subviews of my main view. The result is, they won't be displayed by default. I do have IBOutlets setup and I conditionally add/remove them from my main view hierarchy using the standard addSubview: and removeFromSuperview.

UITabBarController, MoreNavigationController and the Holy Grail of Device Rotation

UPDATE: See my answer to this question first. This appears to be a bug. A minimal test case has been created and a report has been filed with Apple. (Fixed as of iPhone OS 3.1.)
Here's a puzzler from the "I'm so close!" department.
I have a Tab Bar-based iPhone app. Each tab features a UINavigationController with the usual suspects (nav bar, table view ... which in turn can lead to another VC, etc.).
Now, one of those lower-level VCs is to be used in portait and landscape modes. But there's a problem. Our landscape-friendly VC's shouldAutorotateToInterfaceOrientation: won't get called out-of-the-box! What to do?
Here's what we do. In my Tab Bar Controller, which I have implemented in its own file, I have this:
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
return [self.selectedViewController shouldAutorotateToInterfaceOrientation:interfaceOrientation];
}
This ends up propagating the request to my landscape-friendly VC, which also responds to this message. All my other VCs don't implement this method, so they simply go with the default portrait orientation.
Problem solved!!! Yay!
Well, not quite. :(
Seems like things don't go so well when my landscape-friendly VC is invoked from within the depths of the tab bar controller's MoreNavigationController.
I decided to compare/contrast between a VC called from within one of the first four tab bar UINavigationControllers ... and that same VC called from within the MoreNavigationController. This is going to be a bit ultra-detailed, so bear with me. Hopefully the play by play proves useful for sleuthing things out.
When the app loads, there are several initial calls to the tab bar controller's shouldAutorotate... method. In these early cases, selectedViewController is nil. However, we eventually finish loading, the initial tab item is selected, and all is well.
Right. First, let's pick one of the first four tab bar items and drill down to our VC.
We'll pick third nav bar item, so that's the third nav controller. We drill down to our VC that supports rotation. A quick inspection confirms that the parent is indeed the third nav controller from our tab bar's view controller list. Good!
Let's rotate the device. The tab bar controller is asked to autorotate (see the above code). We observe that selectedViewController is also that third nav controller, plus the nav controller's top and visible view controllers are both set to our trusty VC that supports rotation.
Thus, the tab bar controller will forward the shouldAutorotate message over to the third nav controller ... but our rotation-friendly VC ultimately gets the message. (I'm not doing anything special here. Maybe the desired VC gets the message because it's the top and/or visible VC?) In any event, we rotate to landscape, things are resized, and all is well. "Great Success!"
Now let's hit that back button and pop the VC stack, leaving landscape mode in the process. The tab bar controller is queried again.
Time for a little aside here. The topViewController for our nav controller is still that rotation-friendly VC, but visibleViewController is now set to UISnapshotModalViewController! Heh. Never saw this one before ... but Erica Sadun has. Looks like it's for "disappearing view controllers" (which in this case is certainly true - it's disappearing alright).
As I keep stepping through, the visible VC stays as Snapshot, but the top VC eventually changes to the next VC on the stack, since my special VC is eventually gone. Fair enough.
So that's the scenario where everything works well.
Now let's try the same test, only this time we're going to go to the MoreNavigationController (the More tab bar item) and drill down to the same VC class as before. In my case it happens to be the 7th one in the tab bar controller's VC list.
We enter the rotation-aware VC and ... this time it gets asked to rotate directly! The Tab Bar Controller is not asked for permission to rotate at all. Hmm.
A quick check of the parent VC shows it is a MoreNavigationController. OK, that makes sense.
Now let's try to rotate the device. NOTHING GETS CALLED. None of our breakpoints get hit. Not in our VC. Not in our tab bar controller. (Huh?!?!)
O-kaaaay. Let's pop the stack, go back into the same VC and try to rotate again. Weird. NOW we get a call in the Tab Bar Controller asking for autorotation permission. Here, the selected Controller is our trusty Nav controller (#7), but this time its visibleViewController and topViewController are SET TO NIL!
Once we continue from here, a mysterious message appears in the debugger console:
Using two-stage rotation animation. To
use the smoother single-stage
animation, this application must
remove two-stage method
implementations.
Mysterious because I'm not using two-stage rotation animation! No SecondHalf method variants are in play anywhere in my source code.
Alas, my rotation-aware VC is never told that rotation is occurring (even though rotation does occur on-screen), so of course my view is all fouled up as a result. Mayhem and sadness ensue. :(
We won't even bother popping the stack at this point.
I think the View Controller doc hints at the possible problem:
If you want to perform custom
animations during an orientation
change, you can do so in one of two
ways. Orientation changes used to
occur in two steps, with notifications
occurring at the beginning, middle,
and end points of the rotation.
However, in iPhone OS 3.0, support was
added for performing orientation
changes in one step. Using a one-step
orientation change tends to be faster
than the older two-step process and is
generally recommended for any new
code.
I wonder if MoreNavigationController is still responding to the two-step process, and is thus tripping up any attempts to use the one-step process? Note that, if you respond to the two-step messages, the one-step variant will not work (again, per the docs). I'm not responding to them, but I have a sneaking suspicion something behind-the-scenes IS.
In fact, if I comment out the single-step method, and try to respond to willAnimateSecondHalfOfRotationFromInterfaceOrientation:duration:, I do get the memo! But it still doesn't pop off the stack very cleanly (in terms of visuals). Even stranger: willAnimateFirstHalfOfRotationFromInterfaceOrientation:duration: is NOT called, even when I tried to sneak a call to self (using the FirstHalf message) in shouldAutorotateToInterfaceOrientation:. It returns immediately during a trace, as if I never even defined it. Sigh.
So that's the play-by-play.
In summary, has anyone successfully handled one-step device rotation for a VC invoked from within a Tab Bar Controller's MoreNavigationController? Inquiring minds want to know!
Apple advises against subclassing UITabBarController, so I found an easy way to handle autorotation using categories instead. It doesn't fix your bug with the More... view controllers, but I think it's a more Apple-friendly way of getting the job done (and means less subclassing for you).
To make every tab in my application autorotate properly, I've defined -shouldAutorotateToInterfaceOrientation: in my custom view controllers, but they are all inside UINavigationControllers within a UITabBarController, so the message won't get sent down the chain to my VC until those two also respond. So I added the following lines to my app delegate files:
Added to the bottom of MyAppDelegate.h
#interface UITabBarController (MyApp)
#end
#interface UINavigationController (MyApp)
#end
Added to the bottom of MyAppDelegate.m
#implementation UITabBarController (MyApp)
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation {
return YES;
}
#end
#implementation UINavigationController (MyApp)
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation {
return YES;
}
#end
It appears we have a bug. I have created a reproducible, minimal test case, and reported it via Apple Bug Reporter (RADAR Problem 7139857).
Update: This has been fixed as of iPhone OS 3.1.
The essential problem is:
View controllers already on a
Navigation Controller stack do not
receive
willAnimateRotationToInterfaceOrientation:duration:
messages when a Tab Bar Controller's
'More Navigation Controller' is in
use.
This problem does not occur when the tab bar item view controllers are basic view controllers. Only when they are navigation controllers and only when the "More" navigation hierarchy is in use.
The console message (regarding two-stage rotation animation) suggests that something within the framework (the More Navigation Controller?) is still using a two-stage animation, even though single stage is now recommended as of iPhone OS 3.0.
That could explain why willAnimateRotationToInterfaceOrientation: is not called in that particular case. Per Apple's view controler documentation, this message will NOT be invoked when two-stage, first/second-half orientation messages are being responded to instead.
A slightly modified version of Victorb's answer which allows every single view controller to decide if it allows rotation.
Here as a gist for easier copying & forking
AppDelegate.h
#interface UITabBarController (MyApp)
#end
#interface UINavigationController (MyApp)
#end
AppDelegate.m
#implementation UITabBarController (MyApp)
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation {
UIViewController *selectedVC = [self selectedViewController];
if ([selectedVC respondsToSelector:#selector(shouldAutorotateToInterfaceOrientation:)]) {
return [selectedVC shouldAutorotateToInterfaceOrientation:toInterfaceOrientation];
}
//optimistic return - if you want no rotation, you have to specifically tell me!
return YES;
}
#end
#implementation UINavigationController (MyApp)
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation {
UIViewController *visibleVC = [self visibleViewController];
if ([visibleVC respondsToSelector:#selector(shouldAutorotateToInterfaceOrientation:)]) {
return [visibleVC shouldAutorotateToInterfaceOrientation:toInterfaceOrientation];
}
//optimistic return - if you want no rotation, you have to specifically tell me!
return YES;
}
#end

Accessing Views created in Interface Builder

I've created a ChildViewController class, and then a nib that uses that class.
Then I created a BaseView, that includes some buttons, and some text that I'll be changing programmatically.
Then I created two more views (Boy and Girl), that I want to be able to lay behind the baseview so that the background color is different along with some graphics in an ImageView. I've named the views that I created in IB 'Boy' and 'Girl'...
But when I go back to my code where I'm calling ChildViewController, I'm not sure how to access the views I created so I can call insertSubView. Do I need to instantiate them in code? (in ViewDidLoad perhaps?) Does the nib create the instances when it loads?
I'm confused about how to handle multiple views for a single ViewController
edit =================
#Pablo Santa Cruz
Your answer assumes that i have two nibs and two view controllers (one for each view). I want to know if I can use one nib and one controller, and load in UIViews. It seems silly to create another nib and controller, when all want to do is change the background color and some graphics. Can't I programatically load in UIViews into a UIViewController?
Add IBOutlets in your App Controller class in Xcode then link them in IB (ctrl-click or right-click) from the connections tab in the Inspector to the object.
Then you will be able to send method calls to the objects.
The code in Xcode should look like this:
#interface AppController : NSObject
{
IBOutlet Girl girlIvarName1;
IBOutlet Boy boyIvarName2;
}
#end
You can access a UIView programatically by assigning a value to its tag property, which can be set in IB on the first tab of the inspector (Command 1)
The tag value defaults to zero, so if you want to access it specifically, make it non zero and unique. e.g. 100, which I will use in the example code below
Once the tag is set you can access the view using the following code in your UIViewController that was initWithNibName for the NIB containing the tagged view
UIView *aView = [self.view viewWithTag:100];
You can get instances for your IBuilder views with this piece of code:
boyViewController = [[BoyViewController alloc] initWithNibName:#"BoyViewController" bundle:nil];
girlViewController = [[GirlViewController alloc] initWithNibName:#"GirlViewController" bundle:nil];
Assuming your NIB file names are BoyViewController and GirlViewController. With those instances, you can do whatever you need to. I.E., adding them to a parent view (with addSubView message on the parent).