I just started a brandnew project GrafLaboratory to do some experimenting on drawing in NSView.
I added a custom view to the main window and configured it to adopt it´s size when the window changes it´s size.
I modified my InitFrameWith to this:
- (id)initWithFrame:(NSRect)frame
{
self = [super initWithFrame:frame];
if (self) {
NSRect r = [self bounds];
}
return self;
}
When I start my new app the first time and ask for [self bounds] I get the correct value: {{0, 50}, {600, 450}}. I then change the size of the window by dragging with the mouse. When restarting my app initWithFrame returns the same value: {{0, 50}, {600, 450}}.
But I the new changed size. I already tried several things but without success.
Any clues what I´m doing wrong?
Thanks, Ronald
The initWithFrame is only relevant when you initialize the object within a frame.
If it's a custom UIView with custom drawing, then look at:
- (void)drawRect:(NSRect)frame
That will get called when setNeedsDisplay is called and/or the view is resized.
With some custom views, one thing I've done in initWithFrame is to set the autoresize masks:
// subviews autoresize
[self setAutoresizesSubviews:YES];
// set resize masks (springs)
[self setAutoresizingMask:(NSViewMaxXMargin | NSViewMinXMargin |
NSViewMaxYMargin | NSViewMinYMargin |
NSViewHeightSizable | NSViewWidthSizable)];
Then in both init and drawRect, I call my layoutViews metod if the rect has changed:
- (void)drawRect:(NSRect)frame {
...
// called on resize. on init, delegate isn't hooked up so we'll skip
if (!NSEqualRects(_prevRect, [self bounds]))
{
[self layoutViews];
}
// stash away rect for comparison so layout view code only happens when the rect changes
_prevRect = [self bounds];
My layoutViews has custom layout logic.
Hope that helps.
Related
I have a storyboard with 1 UIViewController, holding 1 UIView that contains a number of nested UIViews. I subclassed the View Controller to implement this method:
- (BOOL)shouldAutorotateToInterfaceOrientation: UIInterfaceOrientation)interfaceOrientation {
return (interfaceOrientation == UIInterfaceOrientationLandscapeRight || interfaceOrientation == UIInterfaceOrientationLandscapeLeft);
}
I also added
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UIInterfaceOrientation</key>
<string>UIInterfaceOrientationLandscapeLeft</string>
to the Info.plist.
In the viewDidLoad of the main UIView I'm doing this:
PASectionView* sectionView = [[PASectionView alloc] initWithFrame:CGRectMake(0, 0, self.frame.size.width, 180)];
[self addSubview:sectionView];
The problem is the control is only 756px wide instead of the expected 1024. See the screenshot below for details. I've been searching all over the web but I can't find a solution to this frustrating problem anywhere. I'm using Xcode 4.5 with iOS5.1 set as base SDK.
EDIT
It's working by replacing frame with bounds. However I don't understand what's happening so it isn't working with the frame size.
The frame rectangle, which describes the view’s location and size in
its superview’s coordinate system.
#property(nonatomic) CGRect frame
and
The bounds rectangle, which describes the view’s location and size in
its own coordinate system.
#property(nonatomic) CGRect bounds
Use bounds, not frame.
Set the autoresizingMask to whatever views you want to autoresize on rotate. Like this:
[myView setAutoresizingMask:UIViewAutoresizingMaskFlexibleWidth | UIViewAutoresizingMaskFlexibleRightMargin];
The problem is because before loading into Landscape mode your lines of code is calling the method loading
in which method are you calling this [PASectionView alloc] initWithFrame:CGRectMake(0, 0, self.frame.size.width, 180)];
[self addSubview:sectionView]; if it is view controller class then it should be [PASectionView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, 180)];
[self addSubview:sectionView]; if it is subclass of UIView then where did you get the ViewDidLoad method.
Now to solve your problem
in .h class:
write this
PASectionView* sectionView;
in .m class implement this method
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration{
if (toInterfaceOrientation == UIInterfaceOrientationPortrait)
{
}
else // OrientationLandscape
{
sectionView.frame = CGRect(sectionView.frame.origin.x,sectionView.frame.origin.y,self.view.frame.size.width,180);
}
You have to set the Auto resizing mask for your subview,
Add this line of code before adding the subView to the superview, if your subView is added by code.
yourSubView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
else if your subView added in nib file, select the border magnets and resizing masks in the properties of your subView in the nib file.
In my app I change the position of the standardWindowButtons close / miniturize / expand like so:
//Create the buttons
NSButton *minitButton = [NSWindow standardWindowButton:NSWindowMiniaturizeButton forStyleMask:window.styleMask];
NSButton *closeButton = [NSWindow standardWindowButton:NSWindowCloseButton forStyleMask:window.styleMask];
NSButton *fullScreenButton = [NSWindow standardWindowButton:NSWindowZoomButton forStyleMask:window.styleMask];
//set their location
[closeButton setFrame:CGRectMake(7+70, window.frame.size.height - 22 - 52, closeButton.frame.size.width, closeButton.frame.size.height)];
[fullScreenButton setFrame:CGRectMake(47+70, window.frame.size.height - 22 -52, fullScreenButton.frame.size.width, fullScreenButton.frame.size.height)];
[minitButton setFrame:CGRectMake(27+70, window.frame.size.height - 22 - 52, minitButton.frame.size.width, minitButton.frame.size.height)];
//add them to the window
[window.contentView addSubview:closeButton];
[window.contentView addSubview:fullScreenButton];
[window.contentView addSubview:minitButton];
Now when the window appears with the buttons there is two problems:
1. They are grey and not their correct color
2. when the mouse is over them they do not show the + - or x sign
can anyone tell me what I am doing wrong. Thanks.
Here is the mechanics of this hover magic: Before drawing itself standard circled button (such as NSWindowMiniaturizeButton) calls their superview undocumented method _mouseInGroup:. If this method returns YES circled button draws itself with icon inside. That's all.
If you place these buttons inside your own view, you can simply implement this method and control this mouse-hover-appearance as you want. If you just move or relayout these buttons and they still be subviews of NSThemeFrame (or something similar), you have to swizzle method _mouseInGroup: for this class, and probably it doesn't worth it because we have perfectly simple previous method.
In my case I have custom NSView that contains my standard buttons as subviews and this code makes all described above magic:
- (void)updateTrackingAreas
{
NSTrackingArea *const trackingArea = [[NSTrackingArea alloc] initWithRect:NSZeroRect options:(NSTrackingMouseEnteredAndExited | NSTrackingActiveAlways | NSTrackingInVisibleRect) owner:self userInfo:nil];
[self addTrackingArea:trackingArea];
}
- (void)mouseEntered:(NSEvent *)event
{
[super mouseEntered:event];
self.mouseInside = YES;
[self setNeedsDisplayForStandardWindowButtons];
}
- (void)mouseExited:(NSEvent *)event
{
[super mouseExited:event];
self.mouseInside = NO;
[self setNeedsDisplayForStandardWindowButtons];
}
- (BOOL)_mouseInGroup:(NSButton *)button
{
return self.mouseInside;
}
- (void)setNeedsDisplayForStandardWindowButtons
{
[self.closeButtonView setNeedsDisplay];
[self.miniaturizeButtonView setNeedsDisplay];
[self.zoomButtonView setNeedsDisplay];
}
I'm fully aware that this question is old and Valentin Shergin's answer is correct. It prevents the utilize of any Private API, unlike Google did in Chrome. Just wanted to share a method for those who don't feel like subclass NSView just to put those buttons in an existed view (such as self.window.contentView).
As I just wanted to reposition the NSWindowButtons via setFrame:, I found out that once the window was resized, the tracking areas seems to "fix" themselves automagically, without any Private API usage (at least in 10.11).
Thus, you can do things like the following to apply "fake resize" to the window that you repositioned your buttons:
NSRect frame = [self.window frame];
frame.size = NSMakeSize(frame.size.width, frame.size.height+1.f);
[self.window setFrame:frame display:NO animate:NO];
frame.size = NSMakeSize(frame.size.width, frame.size.height-1.f);
[self.window setFrame:frame display:NO animate:YES];
(I did it within my main window's NSWindowDelegate windowDidBecomeMain:. Should work as long as the window is loaded and visible.)
You're not adding them again. You're moving them to contentView. The buttons are originally in window.contentView.superview.
[window.contentView.superview addSubview:closeButton];
[window.contentView.superview addSubview:fullScreenButton];
[window.contentView.superview addSubview:minitButton];
Should get you the correct behaviour without requiring a trackingArea.
Call [button highlight:yes] for each button.
When subclassing an CALayer and implementing the drawInContext method, I would assume that any drawing I do within there is all that will show up, but instead if I set (for example) borderWidth/borderColor then CALayer will draw a border on it's own above all my custom drawing code.
This is a CALayer subclass:
#implementation MyCustomCALayer
- (id)init
{
self = [super init];
if(self)
{
[self setNeedsDisplayOnBoundsChange:YES];
}
return self;
}
- (void)drawInContext:(CGContextRef)context
{
CGRect rect = CGContextGetClipBoundingBox(context);
CGContextSetFillColorWithColor(context, [UIColor redColor].CGColor);
CGContextFillRect(context, rect);
}
#end
Created in a UIView something like:
- (void)ensureLayer
{
if(myLayer)
return;
myLayer = [[[MyCustomCALayer alloc] init] autorelease];
myLayer.borderColor = [UIColor greenColor].CGColor;
myLayer.borderWidth = 1;
myLayer.frame = CGRectMake(0, 0, self.frame.size.width, self.frame.size.height);
}
- (void)layoutSublayersOfLayer:(CALayer *)layer
{
[super layoutSublayersOfLayer:layer];
[self ensureLayer];
if(![[layer.sublayers objectAtIndex:0] isEqual:myLayer])
[layer insertSublayer:myLayer atIndex:0];
}
What happens, is the MyCustomCALayer fills a rectangle with red, this is what I would expect to see and nothing else, since i've implemented the drawInContext method, but instead I see a red rectangle with a green border on top, always on top, i've tried just about every combination I can think of to get rid of the green border being drawn and cannot figure it out.
My reasoning is I would like to use the borderWidth and borderColor and other properties of the CALayer instead of creating my own properties, because the code that I need to draw contains a border, a fill, etc... but the rendering I need to do is not a simple shape. So the only way i've found around this is to set the borderWidth to 0 and add my own property to my subclass, like myBorderWidth, which is ridiculous.
This is done with the latest iOS SDK, but i'd imagine it's the same for Mac.
Hope this makes sense, any thoughts?
Thanks!
You’re out of luck; CoreAnimation doesn’t support overriding its implementation of rendering for the basic layer properties. Please do file a bug.
I'm currently using the method shown in this Cocoa with Love article to create a custom NSWindow subclass. As in the example, I needed to have a roughly 10px margin around the content of the window in order to draw an arrow (I'm creating a popover style window). I had to have the margin around the entire window instead of just the side with the arrow on it because I wanted to be able to change the arrow position without having to reposition the content.
To summarize, the method I'm using to do this is (relevant code is at the bottom):
Override the contentRectForFrameRect: and frameRectForContentRect:styleMask: methods of NSWindow to add the padding around the content:
Sets the custom drawn frame view of the window as the contentView and then overrides the setter and getter for the contentView so that the view that is passed in is added as a subview of the frame view.
The problem is that the autoresizing masks of views inside the actual content view of the window are completely messed up. Here is how I'm setting up the content in interface builder:
Here's how the autoresizing mask of the table view scroll view is set up:
And here's how the text label's autoresizing mask is set:
And here's what the result looks like in-app:
Relevant code (derived from the aforementioned article)
#define CONTENT_MARGIN 10.0
- (NSRect)contentRectForFrameRect:(NSRect)windowFrame
{
windowFrame.origin = NSZeroPoint;
return NSInsetRect(windowFrame, CONTENT_MARGIN, ICONTENT_MARGIN);
}
- (NSRect)frameRectForContentRect:(NSRect)contentRect
{
return NSInsetRect(contentRect, -CONTENT_MARGINT, -CONTENT_MARGIN);
}
+ (NSRect)frameRectForContentRect:(NSRect)contentRect
styleMask:(NSUInteger)windowStyle
{
return NSInsetRect(contentRect, -CONTENT_MARGIN, -CONTENT_MARGIN);
}
- (NSView*)contentView
{
return _popoverContentView;
}
- (void)setContentView:(NSView *)aView
{
if ([_popoverContentView isEqualTo:aView]) { return; }
NSRect bounds = [self frame];
bounds.origin = NSZeroPoint;
SearchPopoverWindowFrame *frameView = [super contentView];
if (!frameView) {
frameView = [[[SearchPopoverWindowFrame alloc] initWithFrame:bounds] autorelease];
[super setContentView:frameView];
}
if (_popoverContentView) {
[_popoverContentView removeFromSuperview];
}
_popoverContentView = aView;
[_popoverContentView setFrame:[self contentRectForFrameRect:bounds]];
[_popoverContentView setAutoresizingMask:(NSViewWidthSizable | NSViewHeightSizable)];
[frameView addSubview:_popoverContentView];
}
I thought that maybe the popover content was going over the margins somehow, so I drew a border around the content view, but no, everything is as should be. The only issue is that the autoresizing masks of the label and table view inside the content view do not work as they should. Any advice is greatly appreciated.
EDIT: If anyone's interested, I've open-sourced the complete code for this popover window/controller on github as INPopoverController. Includes a sample project in case you want to try and reproduce the issue.
-( void )scaleWindowForHeight:( float )height
{
if (height > 22)
{
NSWindow* window = [self window];
NSRect old_window_frame = [window frame];
NSRect old_content_rect = [window contentRectForFrameRect: old_window_frame];
NSSize new_content_size = NSMakeSize( old_window_frame.size.width, height );
// need to move window by Y-axis because NSWindow origin point is at lower side:
NSRect new_content_rect = NSMakeRect( NSMinX( old_content_rect ), NSMaxY( old_content_rect ) - new_content_size.height, new_content_size.width, new_content_size.height );
NSRect new_window_frame = [window frameRectForContentRect: new_content_rect];
[window setFrame: new_window_frame display:YES animate: [window isVisible] ];
}
else
NSLog(#"window size too small");
}
EVERYTHING WRITTEN HERE ACTUALLY WORKS RIGHT
EXEPT FOR [UIImage imageNamed:] METHOD USAGE
Implementation
I am using model in witch you have a custom UITableViewCell with one custom UIView set up as Cell's backgroundView.
Custom UIView contains two Cell-sized images (320x80 px), one of which is 100% transparent to half of the view. All elements are set to be Opaque and have 1.0 Alpha property.
I don't reuse Cells because I failed to make them loading different images. Cell's reused one-by-one up to 9 cells overall. So I have 9 reusable Cells in memory.
Cell initWithStyle:reuseIdentifier method part:
CGRect viewFrame = CGRectMake(0.0f, 0.0f, 320.0f, 80.0f);
customCellView = [[CustomCellView alloc] initWithFrame:viewFrame];
customCellView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[self setBackgroundView:customCellView];
CustomCellView's initialization method:
- (id)initWithFrame:(CGRect)frame {
if ((self = [super initWithFrame:frame])) {
self.opaque = YES;
self.backgroundColor = [UIColor UICustomColor];
}
return self;
}
Images are being pre-loaded to NSMutableArray as UIImage objects from PNG files with UIImage's imageNamed: method.
They are being set in UITableViewDelegate's method tableView:cellForRowAtIndexPath: and passed through UITableViewCell with custom method to UIView.
And then drawn in UIView's drawRect: overridden method:
- (void)drawRect:(CGRect)rect {
CGRect contentRect = self.bounds;
if (!self.editing) {
CGFloat boundsX = contentRect.origin.x;
CGFloat boundsY = contentRect.origin.y;
CGPoint point;
point = CGPointMake(boundsX, boundsY);
if (firstImage) { [firstImage drawInRect:contentRect blendMode:kCGBlendModeNormal alpha:1.0f]; }
if (secondImage) { [secondImage drawInRect:contentRect blendMode:kCGBlendModeNormal alpha:1.0f]; }
}
}
As you see images are being drawn with drawInRect:blendMode:alpha: method.
Problem
Well, UITableView can't be scrolled at all, it's being struck on every cell, it's chunky and creepy.
Thoughts
Well digging sample code, stackoverflow and forums gave me thought to use OpenGL ES to pre-render images, but, really, is it that hard to make a smooth scrolling?
What's wrong with just using UIImageViews? Are they not fast enough? (They should be if you're preloading the UIImages).
One thing to note is that [UIImage imageNamed:] won't explicitly load the image data into memory. It'll give you a reference which is backed by the data on disk. You can get around this by making a call to [yourImage CGImage].