How to add subview in ListView? - macos

I am developing my first MAC application, i downloaded one Example of PxListView
and i have to added one button and background image on cell xib and bind them with controller
and, when on button click i was set height of that cell is much bigger then other. that is done,
and work fine.
but now i want to develop like after is witch cell has open in that cell i want to add some extra contain (Controller) on it, so how it will possible using given example?
pls help me to give some suggest how it will be done.
for Ex like before click on button
after chick on button i want to develop like

You write
i have to added one button and background image on cell xib and bind them with controller
It sounds like you've subclassed PXListViewCell--for convenience, let's call your subclass TemplateListViewCell--and added a xib from which instances of TemplateListViewCell will be loaded in
+[PXListViewCell cellLoadedFromNibNamed:bundle:reusableIdentifier:]
In addition, there is a[t least one] button in TemplateListViewCell.xib.
You write
when on button click i was set height of that cell is much bigger then other. that is done, and work fine
It sounds like this button has as its action a method on TemplateListViewCell such as
- (IBAction)toggleDetail:(id)sender
{
//Code to grow or shrink the height of [self frame].
//...
}
In my approach to implementing -toggleDetail, two modifications to the PXListView files were necessary:
1. Adding a protocol method
- (void)listView:(PXListView *)aListView setHeight:(CGFloat)height ofRow:(NSUInteger)row;
to the PXListViewDelegate protocol.
2. Adding a property
#property (nonatomic, assign) BOOL expanded;
to PXListViewCell.
My implementation of -toggleDetail looks something like this:
- (IBAction)toggleDetail:(id)sender
{
BOOL wasExpanded = [self expanded];
NSRect oldFrame = [self frame];
CGFloat oldHeight = oldFrame.size.height;
CGFloat newHeight = oldHeight;
CGFloat heightIncrement = 0.0f;
if (wasExpanded) {
heightIncrement = -80.0f; //use whatever value is appropriate
} else {
heightIncrement = 80.0f; //use whatever value is appropriate
}
newHeight += heightIncrement;
[[[self listView] delegate] listView:[self listView] setHeight:newHeight ofRow:[self row]];
[[self listView] reloadData];
BOOL isExpanded = !wasExpanded;
[self setExpanded:isExpanded];
}
It might seem better to use [[self listView] reloadRowAtIndex:[self row]]; in place of [[self listView] reloadData], but unfortunately, this doesn't work: if the user hides the detail--shrinks the cell vertically--new cells which should appear on the screen do not.
You write
that is done, and work fine.
It sounds like you were able to implement successfully a method analogous to -[TemplateListViewCell toggleDetail:].
You write
but now i want to develop like after is witch cell has open in that cell i want to add some extra contain (Controller) on it, so how it will possible using given example? pls help me to give some suggest how it will be done.
It sounds like you want instances of TemplateListViewCell to contain extra views if they are expanded.
It might seem tempting to put this code into -[TemplateListViewCell toggleDetail], but this will not work out as we might hope. The trouble is, we need to handle cases where expanded cells have been scrolled out of view and scrolled back into view.
To get this right, we need to have a notion of expanded which persists beyond the usage of a PXListViewCell subclass instance: we either need to keep track of expansion in the PXListView itself or in its delegate.
The better--but less expedient--design seems to be to keep track of this information in the PXListView itself. For the sake of this question, however, I'll demonstrate how to keep track of cell expansion in the delegate. To do this, I'm expanding the PXListViewDelegate protocol and making other changes to the PXListView files:
1. Adding the methods
- (void)listView:(PXListView *)aListView setExpanded:(BOOL)expanded atRow:(NSUInteger)row;
- (BOOL)listView:(PXListView *)aListView expandedAtRow:(NSUInteger)row;
to PXListViewDelegate.
2. Adding the method
- (void)setCell:(PXListViewCell *)cell expandedAtRow:(NSUInteger)row
{
if ([[self delegate] respondsToSelector:#selector(listView:expandedAtRow:)]) {
[cell setExpanded:[[self delegate] listView:self expandedAtRow:row]];
}
}
to PXListView.
3. Calling -[PXListView setCell:expandedAtRow:] from -[PXListView layoutCells]
- (void)layoutCells
{
//Set the frames of the cells
for(id cell in _visibleCells)
{
NSInteger row = [cell row];
[cell setFrame:[self rectOfRow:row]];
[self setCell:cell expandedAtRow:row];
[cell layoutSubviews];
}
NSRect bounds = [self bounds];
CGFloat documentHeight = _totalHeight>NSHeight(bounds)?_totalHeight:(NSHeight(bounds) -2);
//Set the new height of the document view
[[self documentView] setFrame:NSMakeRect(0.0f, 0.0f, NSWidth([self contentViewRect]), documentHeight)];
}
and from -[PXListView layoutCell:atRow:]:
- (void)layoutCell:(PXListViewCell*)cell atRow:(NSUInteger)row
{
[[self documentView] addSubview:cell];
[cell setFrame:[self rectOfRow:row]];
[cell setListView:self];
[cell setRow:row];
[cell setHidden:NO];
[self setCell:cell expandedAtRow:row];
}
4. Setting _expanded to NO in -[PXListViewCell prepareForReuse]:
- (void)prepareForReuse
{
_dropHighlight = PXListViewDropNowhere;
_expanded = NO;
}
Note: In the sample PXListViewCell subclass, MyListViewCell, distributed with PXListView, the implementation of -[MyListViewCell prepareForReuse] fails to call [super prepareForReuse]. Make sure that this call is made in [TemplateListViewCell prepareForReuse]:
- (void)prepareForReuse
{
//...
[super prepareForReuse];
}
One change needs to be made to -[TemplateListViewCell toggleDetail:]. The line
[self setExpanded:isExpanded];
needs to be replaced by
[[[self listView] delegate] listView:[self listView] setExpanded:isExpanded atRow:[self row]];
Once you've set up your PXListView's delegate to properly handle the new delegate methods, you're ready to override [PXListViewCell setExpanded:] in your subclass TemplateListViewCell:
- (void)setExpanded:(BOOL)expanded
{
if (expanded) {
//add detail subviews
} else {
//remove detail subviews
}
[super setExpanded:expanded];
}
Replace //add detail subviews with your own code which programmatically adds the detail subviews that you want and replace //remove detail subviews with code to remove the detail subviews that you want, checking to see that they are present first.
You write
i want to add some extra contain (Controller) on it
It sounds like you want to add view controllers rather than views to your TemplateListViewCell. To do this, use an NSBox and set the box's contentView to your view controller's view. (For details on this, see this answer.)
If you plan on just showing a single view controller's view in an NSBox on the expanded TemplateListViewCell, you can just (1) add a property to TemplateListViewCell referencing your view controller and (2) add an NSBox to TemplateListViewCell xib and set its contentView to the appropriate view controller's view on [cell setExpanded:YES] and set its contentView to nil on [cell setExpanded:NO].

Related

select a view by clicking osx 10.6

i created a image view
for(int i=0; i<pcount; i++)
{
int x = rand() % 350;
int y = rand() % 350;
NSRect rect = NSMakeRect((x+10),(y+10), 200, 200);
//NSImageView *imageView
imageView1 = [[NSImageView alloc]initWithFrame:rect];
[imageView1 setTag:i];
// imageView = [[NSImageView alloc]initWithFrame:rect];
// [imageView1 rotateByAngle:rand() % 150];
[imageView1 setImageScaling:NSScaleToFit];
[imageView1 canBecomeKeyView];
NSImage *theImage = [[NSImage alloc]initWithContentsOfURL:(NSURL*)[patharray objectAtIndex:(i)]];
[imageView1 setImage:theImage];
[[imageView1 cell] setHighlighted:YES];
[[layoutCustom view] addSubview:imageView1 positioned:NSWindowMovedEventType relativeTo:nil];}
now how can select each image view by mouse click ? thanks in advance.
I'm assuming here that you have your reasons for not using existing collection views. So from what I read in your code you have layoutCustom.view, which contains a bunch of NSImageViews. Here are two options:
In your layoutCustom object implement the mouseDown: (or mouseUp: or both). Take the event location convert it view coordinates and look for any subview for which CGRectContainsPoint(subview.frame, mouseDownPoint) return YES. You should select that view.
Subclass NSImageView and implement mouseDown: (or mouseUp: or both). On mouseDown: simply set a "selected" flag. Either the view can draw something itself when selected or the layoutCustom object can observe the property and draw the selection accordingly.
I would prefer option 1 because it simpler, requires fewer classes and fewer interactions between objects.
// Option 1 (in layoutCustom class)
- (void) mouseDown:(NSEvent*)theEvent {
CGPoint mouseDownPoint = [self convertPoint:theEvent.locationInWindow fromView:nil];
for (NSView *view in self.subviews) {
if (CGRectContainsPoint(view.frame, mouseDownPoint)) {
// Do something to remember the selection.
// Draw the selection in drawRect:
[self setNeedsDisplay:YES];
}
}
}
// Option 2 (in Custom subclass of NSImage)
- (void) mouseDown:(NSEvent*)theEvent {
self.selected = !self.selected;
}
// Option 2 (in layoutCustom class)
- (void) addSubview:(NSView*)view positioned:(NSWindowOrderingMode)place relativeTo:(NSView*)otherView {
[super addSubview:view positioned:place relativeTo:otherView];
[self startObservingSubview:view];
}
- (void) willRemoveSubview:(NSView*)view {
[self stopObservingSubview:view];
}
- (void) startObservingSubview:(NSView*)view {
// Register your KVO here
// You MUST implement observeValueForKeyPath:ofObject:change:context:
}
- (void) stopObservingSubview:(NSView*)view {
// Remove your KVO here
}
I've got a better idea: Instead of fighting with converting mouse clicks in a view to coordinates and then figuring out how to map it to the right subview or sub-image, why not have one big (or scrolling?) view and then add your images as giant "NSButton" objects (set to custom type), where the button images can be the images you want to add.
As for how to select each image? You can either subclass "NSButton" and keep track of some custom data within it, or you can use a "tag" to figure out which button was pressed in your "IBAction" method and then decide what to do with it.
Another approach might be to embed your images into NSTableView cells...

cursorUpdate called, but cursor not updated

I have been working on this for hours, have no idea what went wrong. I want a custom cursor for a button which is a subview of NSTextView, I add a tracking area and send the cursorUpdate message when mouse entered button.
The cursorUpdate method is indeed called every time the mouse entered the tracking area. But the cursor stays the IBeamCursor.
Any ideas?
Reference of the Apple Docs: managing cursor-update event
- (void)cursorUpdate:(NSEvent *)event {
[[NSCursor arrowCursor] set];
}
- (void)myAddTrackingArea {
[self myRemoveTrackingArea];
NSTrackingAreaOptions trackingOptions = NSTrackingCursorUpdate | NSTrackingMouseEnteredAndExited | NSTrackingActiveInKeyWindow;
_trackingArea = [[NSTrackingArea alloc] initWithRect: [self bounds] options: trackingOptions owner: self userInfo: nil];
[self addTrackingArea: _trackingArea];
}
- (void)myRemoveTrackingArea {
if (_trackingArea)
{
[self removeTrackingArea: _trackingArea];
_trackingArea = nil;
}
}
I ran into the same problem.
The issue is, that NSTextView updates its cursor every time it receives a mouseMoved: event. The event is triggered by a self updating NSTrackingArea of the NSTextView, which always tracks the visible part of the NSTextView inside the NSScrollView. So there are maybe 2 solutions I can think of.
Override updateTrackingAreas remove the tracking area that is provided by Cocoa and make sure you always create a new one instead that excludes the button. (I would not do this!)
Override mouseMoved: and make sure it doesn't call super when the cursor is over the button.
- (void)mouseMoved:(NSEvent *)theEvent {
NSPoint windowPt = [theEvent locationInWindow];
NSPoint superViewPt = [[self superview]
convertPoint: windowPt fromView: nil];
if ([self hitTest: superViewPt] == self) {
[super mouseMoved:theEvent];
}
}
I had the same issue but using a simple NSView subclass that was a child of the window's contentView and did not reside within an NScrollView.
The documentation for the cursorUpdate flag of NSTrackingArea makes it sound like you only need to handle the mouse entering the tracking area rect. However, I had to manually check the mouse location as the cursorUpdate(event:) method is called both when the mouse enters the tracking area's rect and when it leaves the tracking rect. So if the cursorUpdate(event:) implementation only sets the cursor without checking whether it lies within the tracking area rect, it is set both when it enters and leaves the rect.
The documentation for cursorUpdate(event:) states:
Override this method to set the cursor image. The default
implementation uses cursor rectangles, if cursor rectangles are
currently valid. If they are not, it calls super to send the message
up the responder chain.
If the responder implements this method, but decides not to handle a
particular event, it should invoke the superclass implementation of
this method.
override func cursorUpdate(with event: NSEvent) {
// Convert mouse location to the view coordinates
let mouseLocation = convert(event.locationInWindow, from: nil)
// Check if the mouse location lies within the rect being tracked
if trackingRect.contains(mouseLocation) {
// Set the custom cursor
NSCursor.openHand.set()
} else {
// Reset the cursor
super.cursorUpdate(with: event)
}
}
I just ran across this through a Google search, so I thought I'd post my solution.
Subclass the NSTextView/NSTextField.
Follow the steps in the docs to create an NSTrackingArea. Should look something like the following. Put this code in the subclass's init method (also add the updateTrackingAreas method):
NSTrackingArea *trackingArea = [[NSTrackingArea alloc] initWithRect:self.bounds options:(NSTrackingMouseMoved | NSTrackingActiveInKeyWindow) owner:self userInfo:nil];
[self addTrackingArea:trackingArea];
self.trackingArea = trackingArea;
Now you need to add the mouseMoved: method to the subclass:
- (void)mouseMoved:(NSEvent *)theEvent {
NSPoint point = [self convertPoint:theEvent.locationInWindow fromView:nil];
if (NSPointInRect(point, self.popUpButton.frame)) {
[[NSCursor arrowCursor] set];
} else {
[[NSCursor IBeamCursor] set];
}
}
Note: the self.popUpButton is the button that is a subview of the NSTextView/NSTextField.
That's it! Not too hard it ends up--just had to used mouseMoved: instead of cursorUpdate:. Took me a few hours to figure this out, hopefully someone can use it.

draw separately in 2 custom views in one class

In IB, put 2 custom views into one window. I don't see any way to give them separate names. In Inspector-Info I had to use the same name, the class name, for both of them in the drop-down menu. I tried
DrawRect: NSRect bounds = [self bounds];
[[NSColor greenColor] set];
[NSBezierPath fillRect:bounds];
which filled both custom views with green. But I would like to fill, draw, etc. independently in each custom view. How can I address each view separately? Multiple custom views, later. Or does Cocoa require only one view per class?
This is probably trivial, but Google and the similar questions list here didn't come up with anything close. There's a lot of about multiple view controllers, but I don't need to switch views.
IB will show you the names of the classes that you can assign to an object. If you have only one custom class (eg. "myCustomClass") then it will only show that one in the drop-down menu.
I think the best solution to your problem, if you want tu use only one class, is to put the drawing code in two separate functions and assign each view an IBOutlet, then call the function from the controller class.
//Add this to your interface
NSNumber *myColor;
//Add/Edit the following functions
- (void)drawRect:(NSRect)aRect
{
//Some code...
if ([myColor intValue]) [self drawGreen];
else [self drawRed];
//Some code...
}
- (void)drawGreen
{
NSRect bounds = [self bounds];
[[NSColor greenColor] set];
[NSBezierPath fillRect:bounds];
}
- (void)drawRed
{
NSRect bounds = [self bounds];
[[NSColor redColor] set];
[NSBezierPath fillRect:bounds];
}
- (void)drawRedOrGreen:(int)aColor
{
myColor = [NSNumber numberWithInt:aColor];
}
You have to add the following two lines to your controller's interface
IBOutlet myCustomClass *customView1;
IBOutlet myCustomClass *customView2;
And you have to set each view's color.
This will set it when it loads for the first time.
- (void)awakeFromNib
{
[customView1 drawRedOrGreen:1]; //Green
[customView2 drawRedOrGreen:0]; //Red
}
This way each view will be coloured differently.
An alternative solution would be to create two separate custom classes (eg. "myCustomClass1" and "myCustomClass2") which would have their own drawing code...

How to change color of divider in NSSplitView?

Can we change the color of the divider?
Apple documentations says, that we can override -dividerColor in subclass of NSSplitView for this, but it doesn't works for me, or my understanding isn't correct. Also I've try create color layer over divider, e.g.:
colorLayer = [CALayer layer];
NSRect dividerFrame = NSMakeRect([[self.subviews objectAtIndex:0] frame].size.width, [[self.subviews objectAtIndex:0] frame].origin.y, [self dividerThickness], self.frame.size.height);
[colorLayer setBackgroundColor:[color coreGraphicsColorWithAlfa:1]];
[colorLayer setFrame:NSRectToCGRect(dividerFrame)];
[self.layer addSublayer:colorLayer];
Not works.
This answer may be late but:
If you are using Interface Builder, it is possible to change the property by going to the Identity Inspector of the NSSplitView (cmd+alt+3) and adding a User Defined Runtime Attribute for dividerColor of the type Color.
Actually, simply subclassing NSSplitView and overriding -(void)dividerColor works, but works only for thin or thick divider.
I've created simple configurable split view like this:
#interface CustomSplitView : NSSplitView
#property NSColor* DividerColor
#end
#implementation CustomSplitView
- (NSColor*)dividerColor {
return (self.DividerColor == nil) ? [super dividerColor] : self.DividerColor;
}
#end
Then in Interface Builder specify custom class for your split view to be CustomSplitView and add new user defined runtime attribute with key path = DividerColor, type = Color and select desired splitter color.
I've tried subclassing - (void)dividerColor too and I'm not sure why it doesn't work even though I know it's being called (and it's in the documentation).
One way to change the color of the divider is to subclass - (void)drawDividerInRect:(NSRect)aRect. However, for some reason, this method isn't called and I've checked all over the web for answers, but couldn't find anything, so I ended up calling it from drawRect. Here is the code for the subclassed NSSplitView:
-(void) drawRect {
id topView = [[self subviews] objectAtIndex:0];
NSRect topViewFrameRect = [topView frame];
[self drawDividerInRect:NSMakeRect(topViewFrameRect.origin.x, topViewFrameRect.size.height, topViewFrameRect.size.width, [self dividerThickness] )];
}
-(void) drawDividerInRect:(NSRect)aRect {
[[NSColor redColor] set];
NSRectFill(aRect);
}
Based on Palle's answer, but with the possibility to change the color dynamically in code, I'm currently using this solution (Swift 4):
splitView.setValue(NSColor.red, forKey: "dividerColor")
If your splitview control is part of a NSSplitViewController, you should use something like this:
splitViewController?.splitView.setValue(NSColor.red, forKey: "dividerColor")
In Swift and on macOS 11 I was able to achieve this by simply subclassing the NSSPlitView and only override drawDivider()
import Foundation
import AppKit
class MainSplitView: NSSplitView {
override func drawDivider(in rect: NSRect) {
NSColor(named: "separatorLinesColor")?.setFill()
rect.fill()
}
}
I had previously tried some of the other way, listed in here and what used to work stopped working with macOS 11... but it seems that this works.
One important point I haven't seen mentioned anywhere is that if you are overriding drawRect in a split view then you must call super -- otherwise drawDividerInRect: is never called. So, it should go something like this:
- (void)drawRect:(NSRect)dirtyRect {
// your other custom drawing
// call super last to draw the divider on top
[super drawRect:dirtyRect];
}
- (void)drawDividerInRect:(NSRect)aRect {
[[NSColor blueColor] set];
NSRectFill(aRect);
}

Creating a reference to a UIImageView from a string

I have a series of UIImageViews which are created in IB. I want to show and hide these depending on some button presses. I currently have a method which says if 1 is pressed then hide 2,3,4 then if 2 is pressed then hide 1,3,4. This works but I am trying to improve my code for an update.
My background is actionscript so I'm not sure if what I am trying to do is the right thing.
I basically want to evaluate a string reference to UIImageView, in AS I would use eval(string).
The method I am using is to create a string from a string and a number, so I get "image1". All works, but then I need to evaluate this into a UIImageView so I can update the alpha value.
Firstly is this possible and if not how should I be doing this? I'm starting to think that having this set up in interface builder maybe isn't helping?
That's probably not a good way to work. What you want is an array of imageViews. Then you just need a numeric index, and you can go through the array of imageViews hiding everything that doesn't have the chosen index.
But how do you get an array of imageViews? See How to make IBOutlets out of an array of objects? It explains how to use IBOutletCollection.
If you have a separate button for each view, put those into an IBOutletCollection too. That way you can have something like this:
- (IBAction) imageButtonPressed:(id) sender;
{
// The sender is the button that was just pressed.
NSUInteger chosenIndex = [[self imageButtons] objectAtIndex:sender];
for (NSUInteger imageIndex = 0; imageIndex < [[self imageViews] count]; imageIndex++)
{
// Hide all views other than the one associated with the pressed button.
if (imageIndex != chosenIndex)
{
[[[self imageViews] objectAtIndex:imageIndex] setHidden:YES];
}
else
{
[[[self imageViews] objectAtIndex:imageIndex] setHidden:NO];
}
}
}
If you really, really need to associate the string image1 with an imageView, you can construct an NSDictionary associating your controls with unique string identifiers for later lookup. NSDictionary is powerful, but I'm drawing a blank on reasons why this would be needed.
NSMutableDictionary *viewLookup;
[viewLookup setObject:[[self imageViews] objectAtIndex:0] forKey:#"image0"];
[viewLookup setObject:[[self imageViews] objectAtIndex:1] forKey:#"image1"];
[viewLookup setObject:[[self imageViews] objectAtIndex:2] forKey:#"image2"];
[viewLookup setObject:[[self imageButtons] objectAtIndex:0] forKey:#"button0"];
// ...
// Can now look up views by name.
// ...
NSString *viewName = #"image1";
UIView *viewFound = [viewLookup objectForKey:viewName];
[viewFound doSomething];

Resources