drawRect not being called for NSTableView cell view - cocoa

I have a view-based NSTableView with my custom view and the normal NSTextField in it. The custom view's background colour should change when a user clicks and it doesn't work because drawRect is not being called. I don't understand why really. Here's the delegate method and custom view code.
MyController
- (void)didAddLabel:(id)sender {
CustomLabel *label = (CustomLabel *)sender;
NSTableRowView *aaa = [_tableView rowViewAtRow:_tableView.selectedRow makeIfNecessary:NO];
MyCustomView *hello = [((NSTableCellView*)[aaa viewAtColumn:0]).subviews objectAtIndex:0];
[hello setBackgroundColor:[label nscolor]];
NSLog(#"Background colour is %#", [hello backgroundColor]);
[hello setNeedsDisplay:YES];
}
My view
class MyCustomView: NSView {
var backgroundColor :NSColor = NSColor.purpleColor()
override func drawRect(dirtyRect: NSRect) {
NSLog("In here")
backgroundColor.setFill()
NSRectFill(dirtyRect)
}
}
Observations: 1) the drawRect is only called upon init of the app. 2) The NSLog row contains the new/selected colour however the background does not change from purple.
Question: why isn't drawRect: called after setNeedsDisplay:?

drawRect: is only called if you don't have layer backing turned on. Are any of the parent views / this view have wantsLayer = YES or have a CALayer assigned to it? In that case, you'll have to use updateLayer: since all children of a layer-backed view are automatically layer-backed.

Related

How to use Storyboard to make popover that can be used in code?

I'm building a collection of forms each of which contains several fields. Some of the fields are UITextFields that will display a date. I've created a new class called DatePickerTextField, a descendant of UITextField. When a DatePickerTextField is tapped I'd like for a UIDatePicker control to appear in a popover.
My question is how do I use the storyboard to implement the popover? I can do a segue when there is a specific, visible control in the scene. But how do I represent a generic popover in the scene that I can attach to any instantiated DatePickerTextField that becomes active?
You can create segue that is not connected to any control but I don't think that there would be way to specify anchor point for popover from code. Another option is to create ViewController that is not connected with any segue. When editing storyboard, create ViewController which will be placed in popover, select it and navigate to Utilities pane->Attributes Inspector. Set Size to Freeform, Status Bar to None, specify unique Identifier that will be used to instantiate ViewController from code. Now you can change the size of ViewController by selecting its View and navigating to Utilities pane->Size Inspector.
After that you can create popover from code:
- (IBAction)buttonPressed:(id)sender {
UIView *anchor = sender;
UIViewController *viewControllerForPopover =
[self.storyboard instantiateViewControllerWithIdentifier:#"yourIdentifier"];
popover = [[UIPopoverController alloc]
initWithContentViewController:viewControllerForPopover];
[popover presentPopoverFromRect:anchor.frame
inView:anchor.superview
permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
}
One caveat is that you need to hold reference to popover as ivar of your class, otherwise it'll crash because UIPopoverController would be released and deallocated after buttonPressed returns:
#interface MyViewController : UIViewController {
// ...
UIPopoverController *popover;
// ...
}
So, I had a similar issue, and in case others might benefit, I figured I'd share it, since I benefit so much from stackoverflow.
This solution allows you to set the anchor of a customizable popover segue. It also allows you to configure the segue to be modal or not (I could not find a way to prevent the segue by dimming the exterior context, so if someone knows how to do that, I would be interested in hearing it); this is accomplished by setting the passthrough views for the popover controller. I also added the capacity to specify a custom view, rather than the view of the source viewcontroller (since I needed this capacity); this portion is not critical to the solution.
DynamicPopoverSegue.h
#interface DynamicPopoverSegue : UIStoryboardPopoverSegue
#property BOOL isModal;
#property UIView* sourceView;
#property CGRect anchor;
#end
DynamicPopoverSegue.m
#implementation DynamicPopoverSegue
- (void)perform
{
if (!self.popoverController.popoverVisible)
{
UIViewController* dst = (UIViewController*)self.destinationViewController;
UIViewController* src = (UIViewController*)self.sourceViewController;
UIView* inView = _sourceView ? _sourceView : src.view;
self.popoverController.contentViewController = dst;
if (!_isModal)
{
[self.popoverController setPassthroughViews:[[NSArray alloc] initWithObjects:inView, nil]];
}
[self.popoverController presentPopoverFromRect:_anchor
inView:inView
permittedArrowDirections:UIPopoverArrowDirectionAny
animated:YES];
}
}
#end
Then you just set your segue to "Custom" in the storyboard, and set the segue class to "DynamicPopoverSegue". In my case, since I wanted to associate it with dynamic layers in a view, I could not set the anchor, so I created the segue by control clicking from the view controller icon in the bar beneath my view controller to the view controller I was using to present the popupover.
To call the popover segue:
[self performSegueWithIdentifier:#"MyPopoverSegue" sender:self];
And to configure the popover segue:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[segue identifier] isEqualToString:#"MyPopoverSegue"])
{
DynamicPopoverSegue* popoverSegue = (DynamicPopoverSegue*)segue;
// set the anchor to wherever you want it to be
popoverSegue.anchor = _destinationFrame;
}
}
- (IBAction)pressItemChooseOprateRoom:(id)sender {
if (isPad){
// UIView *anchor = sender;
UIViewController *viewControllerForPopover =
[self.storyboard instantiateViewControllerWithIdentifier:#"OperateRoomList"];
_myPopover = [[UIPopoverController alloc]
initWithContentViewController:viewControllerForPopover];
CGRect rc=[self getBarItemRc:sender];
[_myPopover presentPopoverFromRect:rc
inView:self.view
permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
[MLControl shared].popover =self;
// [self perfformSegueWithIdentifier:SEGUE_POP_OPERATEROOM sender:self];
}else{
[self iphoneOpenOperateRoomList];
/* [self performSegueWithIdentifier:#"iPhonePushOperateRoom" sender:self];
*/
}
}
-(void)iphoneOpenOperateRoomList{
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
UIViewController *vc = [storyboard instantiateViewControllerWithIdentifier:#"OperateRoomList"];
// if (!index.showTabBar) {
// vc.hidesBottomBarWhenPushed = YES;
// }
[self.navigationController pushViewController:vc animated:YES];
}
Just used the answer from Jonnywho for my SWIFT project. In case you need it:
Here's the SWIFT version:
let anchor: UIView = sender
var viewControllerForPopover = self.storyboard?.instantiateViewControllerWithIdentifier("GameAboutViewController") as! UIViewController?
let popover = UIPopoverController(contentViewController: viewControllerForPopover!)
popover.presentPopoverFromRect(anchor.frame, inView: anchor, permittedArrowDirections: UIPopoverArrowDirection.Any, animated: true)
Add a UIView in the scene dock.
You can add it as a subview to any existing view on the view controller.
You can then toggle it's isHidden property as you require.
You can add multiple such subviews and create many such popups.
This technique will save you from setting up a new View Controller and using segues.

Text color of extra label in view-based NSTableView

In a view-based NSTableView, your custom row and cell views (subclasses of NSTableRowView and NSTableCellView) get their backgroundStyle property set, so you know if the background is light or predominantly dark (for the selected, highlighted row).
This even gets passed to immediate subviews.
Now, the default text label of the table cell view reacts correctly to this, so on a dark background, the text is drawn in a suitable light color.
However, an NSTextField added to provide extra text (with a custom text color set in Interface Builder) does not automatically adhere to this convention.
Is there a simple way in the API to get the text field to play nice, or do I have to subclass it?
Instead of overriding drawRect, you could also do this:
- (void)setBackgroundStyle:(NSBackgroundStyle)backgroundStyle {
NSColor *textColor = (backgroundStyle == NSBackgroundStyleDark) ? [NSColor windowBackgroundColor] : [NSColor controlShadowColor];
self.detailTextField.textColor = textColor;
[super setBackgroundStyle:backgroundStyle];
}
See also here: http://gentlebytes.com/blog/2011/08/30/view-based-table-views-in-lion-part-1-of-2/
Just subclass NSTableCellView then implement drawRect:
- (void)drawRect:(NSRect)dirtyRect
{
// Drawing code here.
if (self.backgroundStyle == NSBackgroundStyleDark) {
[yourTextFieldIVar setTextColor:[NSColor whiteColor]];
} else if(self.backgroundStyle == NSBackgroundStyleLight) {
[yourTextFieldIVar setTextColor:[NSColor blackColor]];
}
}

Cocoa NSView not filling with color

What am I doing wrong?
I have an awakeFromNib method in which I am calling a class that is a subview (GameMap). The class exists and I am able to log in the awakeFromNib method as well as log in GameMap's initWithFrame method, but I cannot get GameMap to draw in the window. Here's my AppController.m file's awakeFromNib method:
-(void) awakeFromNib {
//make new game map
GameMap* newMap = [[GameMap alloc] initWithFrame:NSMakeRect(0.0, 0.0, 1000.0, 500.0)];
[[[NSApp mainWindow] contentView]addSubview:newMap];
[newMap release];
}
and in GameMap.m here's the drawRect method
- (void)drawRect:(NSRect)rect {
[[NSColor whiteColor] set];
NSRectFillUsingOperation(rect, NSCompositeSourceOver);
}
in this same app I am calling two other classes from AppController, all subviews of NSView, MakeCircle and MakeRoom that place either a circle (duh, : D) or a rect with a stroke in the window and they work fine, but they are running off of IBOutlet actions (button clicks). Any help would be appreciated.
*NOTE: I have NSRectFillUsingOperation(rect, NSCompositeSourceOver) but this was also failing wiht NSRectFill(rect).
**I can also log the origin.x/size.width, etc. of the passed rect to GameMap from the initWithFrame, so I know it's there.
(I'm away from my computer for a few hours so don't think I'm being rude for not replying, just wanted to get this question out there before I left.)
Just use NSRectFill(rect); instead of NSRectFillUsingOperation(rect, NSCompositeSourceOver);
Have you set the view's class to the GameMap class instead of NSView) in the Interface Builder? When you do this, the GameMap's drawRect() should get called automatically.

Access IB instantiated NSBox in MyDocument from another class?

I started with the View Swapping code from Hillegass's book Cocoa Programming for MAC OS X. This code uses a popup menu in MyDocument.nib to swap ViewControllers using displayViewController in MyDocument.m partially shown below. I'm trying instead to use rows of a table in a ViewController to swap ViewControllers by calling displayViewController in MyDocument.m from the ViewController that generated the table:
- (void)displayViewController:(ManagingViewController *)vc
curBox: (NSBox *)windowBox
{
// End editing
NSWindow *w = [windowBox window];
BOOL ended = [w makeFirstResponder:w];
if (!ended) {
NSBeep();
return;
}
...
The problem I am having is when I call displayViewController from another ViewController I need to send along the Interface Builder instantiated NSBox in MyDocument.nib so that the view can be swapped inside the NSBox in MyDocument.m. So do to this I need to be able to access the Interface Builder instantiated NSBox in MyDocument.nib from another ViewController.
Does anyone know how to access the Interface Builder instantiated NSBox in MyDocument.nib from another ViewController?
EDIT:
I've made some progress. The view hierarchy is:
Window
ContentView
NSBox
I've been able to get the top level Window and the current ContentView with the following:
NSWindow *w = self.view.window;
NSView *v = [w contentView];
but I still haven't figured out how to get the NSBox that is within the ContentView. I've tried the following but they haven't worked:
NSBox *b = [v container];
NSBox *b = [v content ];
Anyone have an idea?
I found the solution. The NSBox is a subview of the ContentView. So the following works:
NSWindow *w = self.view.window;
NSArray *va = [[w contentView] subviews];
NSBox *b = [va objectAtIndex:0];

How can I add a UIView as a subview of UIViewController when a button clicked?

In my app I need to add a UIView dynamically whenever user taps on a button in my main UIViewController. How can I do this?
Create a new method in your view controller with this signature: -(IBAction) buttonTapped:(id)sender. Save your file. Go to Interface Builder and connect your button to this method (control-click and drag from your button to the view controller [probably your File's owner] and select the -buttonTapped method).
Then implement the method:
-(IBAction) buttonTapped:(id)sender {
// create a new UIView
UIView *newView = [[UIView alloc] initWithFrame:CGRectMake(10,10,100,100)];
// do something, e.g. set the background color to red
newView.backgroundColor = [UIColor redColor];
// add the new view as a subview to an existing one (e.g. self.view)
[self.view addSubview:newView];
// release the newView as -addSubview: will retain it
[newView release];
}

Resources