Custom control in NSCollectionViewItem - objective-c

I want to put a custom control inside the view for my NSCollectionViewItem.
Lets say I have a custom NSView called BoxesView.BoxesView is just a view that draws a predetermined number of boxes in its view. That number of boxes is set in the init method. Lets say I set it to 8.
When I load up the collection view, all the other controls in the view work fine, (buttons, sliders, etc.) but my view won't draw.
If I set a breakpoint in the drawRect method of BoxesView it shows that the number of boxes to draw is 0! If I set a breakpoint in my init method where I set numBoxes to 8, it shows that numBoxes does actually get set to 8. Also, the init method only gets called 1 time even though there are multiple rows in the collection view.
What am I doing wrong?
UPDATE
I was able to get this working by setting the itemPrototype to load from a xib instead of being in the same xib as the NSCollectionViewItem. This is great, except it only works on 10.6 and not 10.5.
UPDATE 2
What I'm trying to do, is stick my custom view inside the view that already existed for the NSCollectionViewItem that already exists. What happens is the member variable mBoxWidth gets blown away and is zero so when it goes to draw it, nothing happens.
#implementation DumbView
- (id)initWithFrame:(NSRect)frame {
self = [super initWithFrame:frame];
if (self) {
mBoxWidth = 3;
}
return self;
}
- (void)drawRect:(NSRect)dirtyRect {
NSRect bounds = self.bounds;
[[NSColor redColor]set];
[NSBezierPath fillRect:NSMakeRect(bounds.origin.x, bounds.origin.y, mBoxWidth, mBoxWidth)];
}
#end

I didn't implement initWithCoder. That fixes everything.

NSCollectionViewItem uses a prototype view, which is duplicated and wired up for each item in the collection's represented objects.
You could go through all the trouble to make an IBPlugin for your custom view (one that exposes the numberOfBoxesToDraw binding), but that's a pain in the ass and there's an easier way: manual bindings.
Using Manual Bindings with NSCollectionView/Item
First, subclcass NSCollectionViewItem, tell IB to use this new subclass, and make sure you have an outlet in it (like boxView) that is connected to your custom view.
Next, subclass NSCollectionView (set IB to use this subclass) and override -newItemForRepresentedObject:. In it, you'll first call super (storing the result to a local variable), then manually bind your "boxView"'s number of boxes to the represented object with the "numberOfBoxes" key you're using in your model.

Have you tried overloading copyWithZone?
I'm guessing your item is getting copied and not directly init'd.

Related

How can I set the size of a UIView init with storyboard?

I am currently using iOS 6.0.
I have a custom UIView that needs to have a certain size. If I programmatically init the view and add it it's fine. However, I can't find a place where I can set the size of the view in the storyboard.
Setting its size in the storyboard doesn't work because the storyboard thinks it's empty and set it's size to zero. Setting its size in viewDidLoad or viewDidAppear doesn't work because later on the size will be overwritten by _applyISEngineLayoutValue.
You can do that in your Interface Builder. Open the storyboard where you have your view and open the utilities menu:
Then you can select a button that looks like a ruler on the top of the utilities menu:
In that menu you can set the size of your view and how you want it to expand.
Also, please make sure you setted your Class' view in the class inspector:
Image token from this site.
Finally, make sure you override the initWithFrame and initWithCoder methods:
- (id)initWithFrame:(CGRect)frame {
return [super initWithFrame:frame];
}
//Needs to be overrided when you set your size in interface builder
- (id)initWithCoder:(NSCoder *)aDecoder {
return [self initWithFrame:[self frame]];
}
I am facing the same problem, and I think the short answer is: you can't rely on the frame or bounds in the view's init method when using storyboards.
When your view is initialized by the segue, it will call the initWithCoder: method rather than other init methods, since it is deserializing your view object from the .xib file. Before storyboards, the frame and bounds would be set by the time the initWithCoder: was called, but that appears to no longer be the case with storyboards -- the iOS code sets those values later.
I've used a couple workarounds, depending on the situation:
If I know the size of the view in advance (for example a specialty view that only supports one size) I set my own frame in the initWithCoder: method.
If I don't know the size of the view, I defer initialization of size-specific things until my view's layoutSubviews method is called.
If it's more convenient, I sometimes just explicitly do the size-specific initialization in my view's ViewController. (Not pretty, but sometimes quick-and-easy. I'm not proud.)
I have the same problem as you, and when I select the view and switch to the attributes inspector , setting
"Simulated Metrics" as follows, I can resize the view in the Size inspector.
Make sure that Size is set to either "Freeform" or "None"

Container View Controllers pre iOS 5

iOS 5 adds a nice feature allowing you to nest UIViewControllers. Using this pattern it was easy for me to create a custom alert view -- I created a semi-transparent view to darken the screen and a custom view with some widgets in it that I could interact with. I added the VC as a child of the VC in which I wanted it to display, then added its views as subviews and did a little animation to bring it on the screen.
Unfortunately, I need to support iOS 4.3. Can something like this be done, or do I have to manage my "alert" directly from the VC in which I want to display it?
MORE INFO
So if I create a custom view in a nib whose file owner is "TapView" and who has a child view that is a UIButton. I tie the UIButton action to a IBAction in TapView.
Now in my MainControllerView I simple add the TapView:
TapView *tapView = [[TapView alloc] init];
[[self view] addSubview:tapView];
I see my TapView, but I can't interact with the UIButton on it and can interact with a UIButton on the MainControllerView hidden behind it. For some reason I am not figuring out what I'm missing...
Not sure if this helps, but, in situations where I've needed more control over potential several controllers, I've implemented a pattern where I have a "master" controller object (doesn't need to be descendent from UIViewController), which implements a delegate protocol (declared separately in it's own file), and then have whatever other controllers I need to hook into declare an object of that type as a delegate, and the master can do whatever it needs to do in response to messages from the controllers with the delegate, at whatever point you need; in your case, that being displaying the alert and acting as it's delegate to handle the button selection. I find this approach to be very effective, simpler and usually cleaner. YMMV ;-)
Regd. your second query, where you are trying to create a custom view using nib. Don't change the FileOwner type, instead set "TapView" for the class property of the top level view object.
Once you have done this, you might experience difficulty when making connections. For that just manually choose the TapView file for making connections.
Also, to load the view you need to load its nib file. For which you can create a class level helper method in TapView class like below
+(TapView *) getInstance
{
NSArray *bundle = [[NSBundle mainBundle] loadNibNamed:#"TapView" owner:self options:nil];
TapView *view;
for (id object in bundle) {
if ([object isKindOfClass:[TapView class]]) {
view = (TapView *) object;
break;
}
}
return view;
}
Now, you get a refrence to you view object like this
TapView *tapView = [TapView getInstance];

Change self.view's class type

I have a app out for testing right now that's almost completely done - just a few bug fixes left. Unfortunately, the customer decided that they'd like the entire main page of the app to be inside of a scroll view (so that there's more room for the table at the bottom). I already have everything set up and I don't really want to move everything and change references. Is there an easy way to change the class of the main view to a scroll view? I've already tried changing the class in IB and setting the class type in the init method. If there isn't I'll probably just throw the top section of the view into a nib file and load it as a custom cell.
-EDIT- I ended up changing the class type in IB and then doing
[(UIScrollView *) self.view setScrollEnabled:YES];
[(UIScrollView *) self.view setContentSize:CGSizeMake(0,2000)];
in viewDidLoad. Thanks for the help, wish I could accept all your answers.
When you are referring to [self view], I am going to assume you mean in a view controller. The view of a view controller can be any view that derives from UIView. Thus a scrollview is completely acceptable.
I don't really want to move everything and change references.
what would you have to move? why would you have to change references? Only thing you should need to do is add a scroll view to your view controller, set the view controllers view to it, and add the current view as a subview to the new scroll view. No references need to be changed, nothing has to be moved.
Refer to loadView method in documentation of view controller.
Here is a simple (untested!) example
- (void)loadView {
UIScrollView *scrollView = [[UIScrollView alloc] init] autorelease];
//Set the properties of scrollview appropriately....
self.view = scrollView;
}
Now the root view of your view controller will be a scroll view.
Note
- As the documentation states, do not do this if you are using interface builder to initialize your views/view controller. I could not tell from your description if this was the case or not. If it is, you should be able to change the view type in interface builder.
You need to set the contentSize property of your scrollview.
Since you are using IB, the easiest way to do this is to put all your UI elements into a view and add this single view to your scroll view. In the viewDidLoad method, set the content size of the scrollview to be the same size as the view that contains all your UI.
As an aside, there are much easier ways to reference views than walking down the view hierarchy, as you seem to be doing. viewcontroller.view.something.tableview. Add a connection to the tableview from your view controller in IB and it doesn't matter where that tableview is in the view hierarchy. You'll always be able to reach it from viewcontroller.tableview, no matter how you rearrange your nibs.
I think you have to use a pointer with proper type. Example for Google Maps: let's say you changed you base view's class to GMSMapView.
MapViewController.h
#property GMSMapView *mapView;
MapViewController.m
-(void)awakeFromNib{
[super awakeFromNib];
self.mapView = (GMSMapView*)self.view;
// ... etc.
}

For an IOS app, the view will not clear its content before drawRect as soon as initWithCoder is added to MainView.m, why?

It is really strange that for a Single View app, I added a MainView class and made the ViewController.xib to use custom class of MainView, and catch the touchesMoved event in ViewController.m, and record the dot (the location).
Then in drawRect inside MainView.m, I draw the dot. So when the program runs, all I see is a single dot on the screen, drawn to where my finger moves to...
However, as soon as I added initWithCoder to MainView.m, the behavior is totally different. It seems like the view won't clear its content before drawing the dot, and all the dots previously drawn remain on screen, but the screen flashes a lot (I am using the new iPad with the new GPU)... seems like there are several "buffers", some with different sets of old dots, and another buffer had different set of old dots... When my finger left the screen, it could be one of those buffers (a random buffer with different set of dots). (one more thing: the background of the screen changed to all black. Before, it was grey or whatever color Interface Builder set the view to). Why does adding the initWithCoder as follow will create such effect?
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super init];
if (self) {
}
return self;
}
Your initWithCoder: method is wrong. You have to call [super initWithCoder:aDecoder], not plain old [super init], otherwise all the information from the xib file (including the redraw options and background colour) is lost - the aDecoder object contains all this information, and you are discarding it.

Loading a Custom ViewController with data

I have a UITableView which loads through it's navigationController a new viewcontroller.
This code goes in the tableView:didSelectRowAtIndexPath method:
ConcertDetailViewController *detailVC = [[ConcertDetailViewController alloc] initWithNibName:#"ConcertDetailViewController" bundle:nil];
The UITableView has a model, I want to sent an element of this model to the newly created ViewController.
detailVC.aProd = [_prod objectAtIndex:indexPath.row];
When the value is set I want the detailVC to draw the data on the screen. I thought a custom setter, overwriting the one generated by #synthesize would work.
-(void)setaProd:(NSMutableDictionary *)aProd {
_aProd = aProd;
[self displayAProd];
}
displayAProd just takes the values in aProd and put's them on the screen, or rather I'm setting some value of an outlet , created in my nib file.
self.prodNameLbl.text = [_aProd objectForKey:#"name"];
Nothing special about this. But it just doesn't work. I figured out why, I think.
It's because the setter executes way faster then, loading the whole view into memory.
If I put self.prodNameLbl.text = #"something"; in the viewDidLoad method it does display the correct value in the label.
A quick workaround would be the see if _concerts has been set and from there call displayAProd. Here I'm doubting myself if it's a good way to load a view. What if the custom setter takes longer to execute the loading the view. The test to see if _concerts has been set will be false and nothing will be displayed. Or is that just impossible to happen ?
Or maybe there's a better pattern for loading views and passing data to them to be displayed.
Thanks in advanced, Jonas.
The problem is that when you load the view controller from the NIB, the IBOutlets will not be connected to your UILabel and other similar properties during the initWithNibName call.
You need to wait for viewDidLoad to be called on detailVC and call [self displayAProd] from there. At this point, the connections will have been made.
Do a quick test. Put a break point in your didSelectRowAtIndexPath method and, after initialising detailVC, check to see if prodNameLbl is null or not.