I want to iterate thru all controls existing in a View on iOS when the view is loaded.
Also, it would be acceptable to have a callback that is called when these controls are initialized.
How can I obtain this?
To iterate through subviews (controls) in a parent view, call the parent view's -subviews method:
for (UIView *subview in [parentView subviews]) {
/* do something with subview */
}
You could subclass the control, creating a custom control. Here, you override the custom control view's initializer method. In this method, you would call the parent's initializer, and add your custom code.
for (UIView *aView in [myView subviews]) { //do something with aView }?
Related
Am new to Mac apps, and am writing one simple app which has a common layout for different sections of the app. Its basically an image with one or two buttons(titles keep varying) in all sections.
So I thought of creating a CustomNSView with one Image Well and two rounded buttons in a new Nib file and in a separate class file (MyCustomView, which is a subclass of NSView) it would load this Nib in initWithframe method. So now when ever I drag drop a custom view and set its class to MyCustomView I get the Image and two buttons instantly without any additional code. But now how would I control these buttons(outlets/actionms) in other View Controllers ? The same view will be used every where so I cannot set files owner in nib to the view controller ?
Is it right in doing so ? Is there any way to create a custom view which would delegate all button actions to which ever view controller its included in ?
You can write your custom delegate. Though using that you can send messages from one object to another
Here's how I would do it. I wouldn't create a CustomNSView, I would create a CustomViewController (with its xib file included). On that CustomViewController, I would design the two buttons and setup CustomViewController.h like so.
#property (nonatomic, weak) id delegate; // Create a delegate to send call back actions
-(IBAction)buttonOneFromCustomVCClicked:(id)sender;
-(IBAction)buttonTwoFromCustomVCClicked:(id)sender;
CustomViewController.m like so.
-(void)buttonOneFromCustomVCClicked:(id)sender {
if ([self.delegate respondsToSelector:#selector(buttonOneFromCustomVCClicked:)]) {
[self.delegate buttonOneFromCustomVCClicked:sender];
}
}
-(void)buttonTwoFromCustomVCClicked:(id)sender {
if ([self.delegate respondsToSelector:#selector(buttonTwoFromCustomVCClicked:)]) {
[self.delegate buttonTwoFromCustomVCClicked:sender];
}
}
In interface builder of your customViewController, link up both button's SentAction event to the two methods (they should be displayed in file's owner).
Then in your otherClass in which you want to load the generic custom view, instantiate the generic view controller like so.
#import "customViewController.h"
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
customViewController *newCustomViewController = [[ViewController alloc] initWithNibName:#"customViewController" bundle:nil];
[newCustomViewController setDelegate:self];
self.backGroundView = [newCustomViewController view]; // Assuming **backGroundView** is an image view on your background that will display the newly instantiated view
}
-(void)buttonOneFromCustomVCClicked:(id)sender {
// Code for when button one is clicked
}
-(void)buttonTwoFromCustomVCClicked:(id)sender {
// Code for when button two is clicked
}
I have defined a custom class with a drawRect called Drawing. I have laid out the view in my NIB to contain an NSScrollView with a Drawing subview. When I launch the program, the screen is blank. Interestingly, when I create the documentView for the NSScrollView programmatically, I get an image in my scroll view. When I use the instance for the nib in my setDocumentView, I get nothing.
So if the Drawing view is set in IB,
[_scrollViewWorkspace setDocumentView:_drawing]; //does not work.
But
[_scrollViewWorkspace setDocumentView:[[Drawing alloc] initWithFrame:NSMakeRect(0,0,[[_scrollViewWorkspace documentView ]bounds].size.width, [[_scrollViewWorkspace documentView] bounds ].size.height)]];
Works great!
Why can't I statically bind a drawing object in a NIB?
When you set the custom class of the NSView subclass in the nib file to your custom class, at runtime, an instance of that class will be created using initWithCoder: rather than initWithFrame:.
You can easily check to see if this is the case by adding the following method to your custom Drawing view:
- (id)initWithCoder:(NSCoder *)coder {
NSLog(#"[%# %#]", NSStringFromClass([self class]), NSStringFromSelector(_cmd));
return [super initWithCoder:coder];
}
Then launch your app and see if [Drawing initWithCoder:] is logged to the console.
If you have overridden initWithFrame: in your Drawing class to do custom initialization (like set a custom image automatically), you will need to add that same code to the initWithCoder: method so that you end up with the same result:
- (id)initWithCoder:(NSCoder *)coder {
if ((self = [super initWithCoder:coder])) {
// do custom initialization here
}
return self;
}
Solved!
When I laid the view out in Interface Builder I just dragged the custom view to the NSScrollView in the canvas. I re-did the layout so that I used Embed->Scroll View and it worked.
Bruce
I'm new to Xcode and objective c. I want to create a button (probably a UIBarButtonItem, for a navigation bar) with a particular appearance, which I will use repeatedly in different views. I've searched at length but can't figure out how.
Would it be appropriate to subclass UIBarButtonItem? I tried to do that, but I was quickly in over my head. Once I create the .h and .m files as a subclass of UIBarButtonItem, do I then have to instantiate a UIBarButtonItem? Do those files not automatically create a button object for me (imported from the parent class), which I can refer to as self? It seems like it would be weird to instantiate a button within its own subclass.
One thing I want to do is add the line,
button.titleLabel.lineBreakMode = NSLineBreakByWordWrapping;
but I'm at a loss as to how to create reusable button with that property.
Even if that is completely the wrong approach to creating a reusable custom button, I clearly need to improve my understanding of objects, so explanation of my misunderstandings would be much appreciated!
Please?
You can do this without subclassing - by making a category (a preferred way of doing things in Objective-C). With a category you can provide custom methods for an object without having to subclass it. You can't (easily) provide custom properties, but in your case this is not relevant.
Using a Category
This is how your category header file could look:
// UIButton+StyledButton.h
#import <UIKit/UIKit.h>
#interface UIButton (StyledButton)
- (void) styleButton;
#end
Then in the implementation file:
//
// UIButton+StyledButton.m
//
#import "UIButton+StyledButton.h"
#implementation UIButton (StyledButton)
- (void) styleButton {
//style your button properties here
self.titleLabel.lineBreakMode = NSLineBreakByWordWrapping;
}
('self' refers to the button object, which also acquires the custom methods you write in the category.)
To use it, #import "UIButton+StyledButton.h" then you can do this sort of thing...
on viewDidLoad {
[super viewDidLoad];
UIButton* myButton = [[UIButton alloc] initWithFrame:myFrame];
[myButton styleButton];
}
Using a Subclass
The subclassed equivalent would look something like this:
The header file...
// MyCustomButton.h
#import <UIKit/UIKit.h>
#interface MyCustomButton : UIButton
- (id)initWithCoder:(NSCoder *)coder;
- (id)initWithFrame:(CGRect)frame;
#end
The implementation file...
// MyCustomButton.m
#import "MyCustomButton.h"
#implementation MyCustomButton
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self styleButton];
}
return self;
}
- (id)initWithCoder:(NSCoder *)coder
{
self = [super initWithCoder:coder];
if (self) {
[self styleButton];
}
return self;
}
- (void) styleButton {
//style your button properties here
self.titleLabel.lineBreakMode = NSLineBreakByWordWrapping;
}
You provide two init methods - initWithFrame is the method to call when alloc/initing the object in code; initWithCoder is the init method called by the system if loading the object from a storyboard or xib.
To create one of your custom buttons in code, you alloc/init as you would any other object:
MyCustomButton* button = [[MyCustomButton alloc] initWithFrame:buttonFrame];
You wouldn't also alloc/init the superclass instance, this is done by the initWithFrame: method in the subclass when it calls [super initWithFrame:frame]. self refers to your custom subclass instance, but that includes all of the (public) properties and methods from it's superclass - unless you have implemented overrides in the subclass.
To use your subclassed button in a storyboard/xib, just drag out a regular button then set it's type to your custom button class in the Identity Inspector. The initWithCoder method is called automatically when the button is loaded from the storyboard/xib into a view.
update
From your comments, you seem to harbour a few confusions still, so here are some highly compressed de-obfuscating notes...
Keep away from subclassing UINavigationController unless you really know what you are doing. It's rarely necessary.
The buttons on a navController's interface are properties of it's contained viewControllers. Look up the navigationItem property of UIViewController (similarly - in the case of a UIToolbar - the View Controller has a toolbarItems property). This allows Navigation Controllers to be context-aware.
The 'viewDidLoad' in my example is assumed to be in a regular UIViewController. My example is also a category on the regular UIBUtton which has no formal relationship with UIBarButtonItem.
Try getting a UIButton category to work with a regular ViewController first before experimenting with UIBarButtonItem (which does not inherit from UIButton).
UIBarbuttonItem has no initWithFrame, because the thing that organises the bar (UINavigationBar or UIToolbar) - in this case a Navigation Controller - is responsible for it's ultimate size and positioning. The viewController governs the relative order of barButtonItems, and whether they appear on the left or the right, and the content and (some aspects of) it's appearance, but the rest is up to the NavController.
I'm trying to customize the disclosure arrow appearance in my view-based NSOutlineView. I saw that it's recommended to use
- (void)outlineView:(NSOutlineView *)outlineView willDisplayOutlineCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn item:(id)item
delegate method to achieve it. The problem is that this method is not called for some reason. I have 2 custom cell views - one for item and second for header item. May be this method is not called for view-based outline views? May be something became broken in Lion?
Please shed some light.
Solution 1:
Subclass NSOutlineView and override makeViewWithIdentifier:owner:
- (id)makeViewWithIdentifier:(NSString *)identifier owner:(id)owner {
id view = [super makeViewWithIdentifier:identifier owner:owner];
if ([identifier isEqualToString:NSOutlineViewDisclosureButtonKey]) {
// Do your customization
}
return view;
}
For Source Lists use NSOutlineViewShowHideButtonKey.
Solution 2:
Interface Builder
The button is added to the column and the identifier set to NSOutlineViewDisclosureButtonKey.
Official documentation from NSOutlineView.h
/* The following NSOutlineView*Keys are used by the View Based NSOutlineView to create the "disclosure button" used to collapse and expand items. The NSOutlineView creates these buttons by calling [self makeViewWithIdentifier:owner:] passing in the key as the identifier and the delegate as the owner. Custom NSButtons (or subclasses thereof) can be provided for NSOutlineView to use in the following two ways:
1. makeViewWithIdentifier:owner: can be overridden, and if the identifier is (for instance) NSOutlineViewDisclosureButtonKey, a custom NSButton can be configured and returned. Be sure to set the button.identifier to be NSOutlineViewDisclosureButtonKey.
2. At design time, a button can be added to the outlineview which has this identifier, and it will be unarchived and used as needed.
When a custom button is used, it is important to properly set up the target/action to do something (probably expand or collapse the rowForView: that the sender is located in). Or, one can call super to get the default button, and copy its target/action to get the normal default behavior.
NOTE: These keys are backwards compatible to 10.7, however, the symbol is not exported prior to 10.9 and the regular string value must be used (i.e.: #"NSOutlineViewDisclosureButtonKey").
*/
APPKIT_EXTERN NSString *const NSOutlineViewDisclosureButtonKey NS_AVAILABLE_MAC(10_9); // The normal triangle disclosure button
APPKIT_EXTERN NSString *const NSOutlineViewShowHideButtonKey NS_AVAILABLE_MAC(10_9); // The show/hide button used in "Source Lists"
This answer is written with OS X 10.7 in mind, for newer versions of OS X/macOS, refer to WetFish's answer
That method does not get called because it is only relevant for cell based outline views.
In a view based outline view, the disclosure triangle is a regular button in the row view of expandable rows. I don't know where it gets added, but it does, and NSView's didAddSubview: method handles exactly that situation of a view being added somewhere else.
Hence, subclass NSTableRowView, and override didAddSubview:, like this:
-(void)didAddSubview:(NSView *)subview
{
// As noted in the comments, don't forget to call super:
[super didAddSubview:subview];
if ( [subview isKindOfClass:[NSButton class]] ) {
// This is (presumably) the button holding the
// outline triangle button.
// We set our own images here.
[(NSButton *)subview setImage:[NSImage imageNamed:#"disclosure-closed"]];
[(NSButton *)subview setAlternateImage:[NSImage imageNamed:#"disclosure-open"]];
}
}
Of course, your outline view's delegate will have to implement outlineView:rowViewForItem: to return the new row view.
Despite the name, frameOfOutlineCellAtRow: of NSOutlineView still gets called for view based outline views, so for the positioning of your triangle, you might want to subclass the outline view and override that method, too.
For Swift 4.2 macOS 10.14, #WetFish's answer can be implemented as follows:
class SidebarView: NSOutlineView {
override func makeView(withIdentifier identifier: NSUserInterfaceItemIdentifier, owner: Any?) -> NSView? {
let view = super.makeView(withIdentifier: identifier, owner: owner)
if identifier == NSOutlineView.disclosureButtonIdentifier {
if let btnView = view as? NSButton {
btnView.image = NSImage(named: "RightArrow")
btnView.alternateImage = NSImage(named: "DownArrow")
// can set properties of the image like the size
btnView.image?.size = NSSize(width: 15.0, height: 15.0)
btnView.alternateImage?.size = NSSize(width: 15.0, height: 15.0)
}
}
return view
}
}
Looks quite nice!
Swift2 version of #Monolo's answer:
override func didAddSubview(subview: NSView) {
super.didAddSubview(subview)
if let sv = subview as? NSButton {
sv.image = NSImage(named:"icnArwRight")
sv.alternateImage = NSImage(named:"icnArwDown")
}
}
I have a UIViewController with an xib and using Interface Builder I've added a child UIView.
Within the child UIView, when I click on an object within that view, I want to be able to alter the title of the whole window.
Now I'd normally do that setting
self.title = #"hi";
on the parent UIViewController. But is there any way I can access the parent title from within the child?
I've tried
self.superview.title = #"i";
self.parentViewController.title = #"hi";
but neither work.
Any help much appreciated
thanks
self.superview.title = #"i"; evaluates to an object of type UIView, and UIView has no title property. UIViewControllers have a parentViewController property but UIViews don't.
So the fundamental problem is that you're not properly separating your controller and your view classes. What you'd normally do is make the view you want to catch taps on a subclass of UIControl (which things like UIButton already are, but if it's a custom UIView subclass then you can just change it into a UIControl subclass since UIControl is itself a subclass of UIView), then in your controller add something like:
- (void)viewDidLoad
{
[super viewDidLoad];
// we'll want to know if the view we care about is tapped;
// we've probably set up an IBOutlet to it but any way of
// getting to it is fine
[interestingView
addTarget:self
action:#selector(viewTapped:)
forControlEvents:UIControlEventTouchDown];
// UIButtons use UIControlEventTouchUpInside rather than
// touch down if wired up in the interface builder. Pick
// one based on the sort of interaction you want
}
// so now this is exactly like an IBAction
- (void)viewTapped:(id)sender
{
self.title = #"My new title";
}
So you explicitly don't invest the view with any knowledge about its position within the view hierarchy or how your view controllers intend to act. You just tell it to give you a shout out if it receives a user interaction.