I just created a new Xcode project. In the AppControl class Header file I have the following objects defined (and some other ones, too):
IBOutlet NSImageView *inputImageView;
IBOutlet NSImageView *outputImageView;
IBOutlet NSTextField *myNoiseLevel;
IBOutlet CGFloat *mySharpness;
After putting the basic code into the .h and .m files, I then went into Interface Builder and created my UI. I was able to bind the two NSImageView controls in IB to the corresponding NSImageView objects listed above. And I was able to bind a couple of other objects/controls, also. But I am NOT able to bind the last two items listed (myNoiseLevel and mySharpness) to the NSSlider controls I have on the application main window. I'm not sure why. I know this kind of thing is probably hard to diagnose, because it is not "strictly code related," but if there is something "tricky" about binding sliders please let me know what the main "suspects" are that I should check.
This is my first attempt to use a slider control through IB. I have a book (Cocoa programming for Mac OS X, 3rd ed., by A. Hillegass) that I am using to learn about the basic way to do this stuff. And he has a slider example in there. But his slider example is "continuous" and it uses key path binding. I think this is overkill for what I want/need to do -- I just want to pull the value from the slider when another button is pushed (no need for "continuous" update). So I am trying to directly bind the "outlets" listed when I right-click on my App Control object (one for each of those items shown above), to the slider controls on my window. But when I cntl-drag from the AppControl outlet up to the corresponding slider, the slider will not "accept" the arrow I'm dragging.
Does this make sense? Any idea what I'm doing wrong and/or what I need to do to make the binding work? I have tried saving / building / closing & reopening IB and Xcode -- all to make sure IB has the latest version of everything. Still no luck, though.
One last thing ... What I really need are CGFloat numbers, from the slider. Can I simply declare the Outlet as CGFloat type ... or do I need to define it as NSTextField (or something else), and then convert it to Float in my program? You can see in the IBOutlets I pasted above, that I was trying different data types for the outlets (trying to see if my defining them as CGFloat was somehow preventing the bindings).
Make the outlet an NSSlider*. You should then be able to connect to it. When you need the value (eg, in response to the button press you mention) call [yourSliderOutletName doubleValue].
More generally: an IBOutlet is an ivar that can be filled in with a pointer to the actual object awoken from a NIB file. As such, it needs to be of an appropriate type to hold that pointer -- the object's actual class, or one of its superclasses or protocols, or (least informatively) id. You can't just arbitrarily connect an object to any old variable, like your CGFloat. There's no implicit conversion -- how is the system supposed to know what you want?
Related
As I understand it, each element such as a button or label is linked from Storyboard to ViewController. This is done graphically in XCode by dragging. Where is the code that states this pairing in the XCode environment?
There is no code that states this. In fact, there is no actual connection being made at all. It's just an illusion.
There are two separate things being configured when you do that:
In the nib (the storyboard), an outlet is configured. It is just a name, a string
In the code, there is a property with the same name.
The connection between them happens, if it is going to happen at all, when the app actually runs and at some point the nib is loaded. At that moment, the nib has an owner, and key-value coding is used to look for a property of that owner with the same name as the outlet. If it's there, the object instantiated from the nib is assigned as the value of that property, and thus the connection is made.
So you see, there is no real connection in Xcode. There are two separate things that will need to try to connect while the app actually runs, at nib-loading time. It's a fragile and risky business. That is why, if there is no such property, your app crashes at that moment.
Basically #IBOutlet is not part of Swift, and that's what lets Xcode figure out that outlet property inside a controller is linked to some UIView (or subclass, such as UILabel) on the Storyboard.
Also, if you right-click the Storyboard, and Open As > Source Code, you should see a lot of the generated UI/XML code. If your label is called myLabel, you should see something like this:
<connections>
<outlet property="myLabel" destination="zLp-2i-ru4" id="Zda-UD-1h6"/>
</connections>
inside one of the Scene Nodes.
I'm fairly new to Mac development and am slightly confused by the new "storyboard" feature in Xcode 6. What I'm trying to do is segue from one view controller to another in the same window. As of right now, all the different NSViewControllerSegues present the view controller in a new window, be it a modal or just another window. What I'd like to do is just segue within the same window, much in the same way one would on iOS (though an animated transition is not crucial). How would this be achieved?
If you provide a custom segue (subclass of NSStoryboardSegue) you can get the result you are after. There are a few gotchas with this approach though:
the custom segue will use presentViewController:animator so you will need to provide an animator object
because the presented view is not backed by a separate Window object, you may need to provide it with a custom NSView just to catch out mouse events that you don't want to propagate to the underlying NSViewController's view
there's also a Swift-only glitch regarding the custom segue's identifier property you need to watch out for.
As there doesn't seem to be much documentation about this I have made a small demo project with custom segue examples in Swift and Objective-C.
I also have provided some more detail in answer to this question.
(Reviving this as it comes up as first relevant result on Google and I had the same problem but decided against a custom segue)
While custom segues work (at least, the code given in foundry's answer worked under Swift 3; it needs updating for Swift 4), the sheer amount of work involved in writing a custom animator suggests to me that their main use case is custom animations.
The simple solution to changing the content of a window is to create an NSWindowController for your window, and to set its contentViewController to the desired viewController. This is particularly useful if you are following the typical pattern of storyboards and instantiate a new ViewController instance every time you switch.
However.
The NSStoryboard documentation says, quite clearly in macOS, containment (rather than transition) is the more common notion for storyboards which led me to look again at the available tools.
You could use a container view for this task, which adds a NWViewController layer instead of the NSWindowController outlined above. The solution I've gone with is to use an NSTabViewController. In the attributes inspector, set the style to 'unspecified', then select the TabView and set its style to 'tabless'.
To change tabs programatically, you set the selectedTabViewItemIndexof your TabViewController.
This solution reuses the same instance of the ViewControllers for the tab content, so that any data entered in text fields is preserved when the user switches to the other 'tab'.
Simple way with no segues involved to replace the current view controller in the same window:
if let myViewController = self.storyboard?.instantiateController(withIdentifier: "MyViewController") as? MyViewController {
self.view.window?.contentViewController = myViewController
}
So I'm just trying to create a very simple app for demo purposes here:
Created a Single View Application, using storyboards
Added a UIView to the storyboard
Added the following code to my controller's header file:
#property (weak, nonatomic) IBOutlet UIView *myView;
Now, I understand that I can link the UIView to the controller by:
arranging my code such that the header file is next to the storyboard
holding down Ctrl key and dragging it to the property in the header file
My question is this: can I do this without Ctrl-drag? And if so, how?
More specifically - it's annoying to have to put both my header file and storyboard on screen at the same time, and it seems there should be a way to make this connection without doing so.
I also understand that I can manually place the view by creating it inside my controller's viewDidLoad function, but I'd really like to use the interface builder to simplify / visualize things.
Edit: Is the answer to my question affected whether I use storyboards or xib/nib files? (I'd switch to use the one where it works)
you should be able to right click the element, and drag the "referencing outlet" item to the view's "File's Owner" in interface builder. There, it will give you a list of all available IBOutlets (matching the object's type).
In addition to Dima's answer, you can just as well use the Connection inspector in the Utilities pane
I am pretty new to coding and would like to know how to switch XIB views on a button click. IS there also anyway to add animation when switching?
Thanks,
Kevin
this is totally possible, but there are a few things you'll need to do. I imagine you are already familiar with connecting outlets to objects in your XIB, so the first thing you need to do is create the custom views in your XIB and connect them to outlets in your appDelegate. I suggest that one of the views be dragged into the window and one one be outside the window. That way, when the window loads, it already has one of your custom views as a subview. This just makes it easier to get started.
Then you're going to write an IBAction in the appDelegate and connect it to your button. Assuming that one of the custom views is already being hosted by the window, the IBAction should send a replaceSubviewWith message to the window's contentView animator like this [[window.contentView animator] replaceSubview:firstView with:secondView]; where firstView and secondView are the pointer/outlets that you declared and connected to the views in your XIB.
This is sending the animator proxy of the window's content view a message which tells it to replace the old subview with the new one. The reason for sending the message to the view's animator proxy (and not the view itself) is that the transition will be carried out with the deafult CATransitionAnimation. Because you want it to be animated, right?
The reason why you shouldn't remove one subview and then add another is because animating the removal of a subview is actually quite tricky and requires the implementation of the delegate method animationDidEnd. This is because executing an animation on a view that has been removed from the view heirarchy does not make sense. I don't know why Apple hasn't changed this, but for now it will be one of the enduring quirks of CoreAnimation.
Let me know if that helps. I am happy to clarify! And welcome to Cocoa!
An easy way to do this is to use a tabless NSTabView- you can lay everything out in IB so the pain is minimal.
I have a UIView filled with buttons that are all nicely hooked up to actions and outlets. However, in my infinite wisdom, I decided that I really would rather have the button behavior to be different and subclassed a UIControl.
My plan was to hop into Interface Builder, change the class of the buttons to my new UIControl subclass, and then be up and running. This would preserve all of outlet and action connections.
In IB (View Identity Inspector) when I type in my UIControl subclass into the class field, it reverts back to UIButton when I tab out. Any UIButton subclass works, but not a UIControl. I can go down the inheritance tree but not up....
The first plan was to go to XCode and change the superclass of my new control temporarily to UIButton, change the 'class' of my IB buttons, and then change the XCode code superclass back to UIControl. IB accepted and changed the class, but running the app gives me non-visible buttons. The IB Attributes inspector still shows it as a button.
Creating the control from scratch and rewiring works, but I was hoping to not rewire all the buttons if it could be avoided. (This is a change I was hoping to roll across multiple apps, so it is a bit more painful that it sounds)
Anyone know any way around this?
many thanks!
I'm a year late on this, but maybe it will help someone in the future. Perhaps you could open the .xib in a text editor and figure out what text you would have to change to get it to work (try it on a sample project first), then use find and replace to fix all of them at once.