How to align a subview to the center of a parent NSView - cocoa

I'm developing a program in which I am programmatically adding an NSImageView to a custom NSView class. While creating the image-view, I am passing the frame of the parent container.
-(NSImageView *)loadNSImage:(NSString *)imageName frame:(NSRect)frame{
imageName = [[NSBundle mainBundle] pathForResource:imageName ofType:#"png"];
NSImage *image = [[NSImage alloc] initWithContentsOfFile:imageName];
NSImageView *imageView = [[NSImageView alloc]initWithFrame:frame];
[imageView setImage:image];
[imageView setImageScaling:NSImageScaleProportionallyUpOrDown];
[imageView setAutoresizingMask:NSViewHeightSizable | NSViewWidthSizable | NSViewMaxXMargin | NSViewMaxYMargin | NSViewMinXMargin | NSViewMinYMargin];
[imageView setImageAlignment:NSImageAlignCenter];
[image release];
return imageView;
}
Then I am using the addSubView method to add this into the Custom View. The problem is that the image sticks to the bottom-left of the parent view. How can I place this image in the center of the parent view?
I have tried adding an offset to the frame origin, but that doesn't really work when the window is resized, or if an image with a different size is loaded.
Any help would be appreciated.

I'm not sure if this is the best way to do it, but I just do simple math when I want to center a subview inside its parent.
You need to set all the margins to be auto-resizable if you want it to stay centered.
[subview setFrameOrigin:NSMakePoint(
round((NSWidth([parentView bounds]) - NSWidth([subview frame])) / 2),
round((NSHeight([parentView bounds]) - NSHeight([subview frame])) / 2)
)];
[subview setAutoresizingMask:NSViewMinXMargin | NSViewMaxXMargin | NSViewMinYMargin | NSViewMaxYMargin];
This is just calculating the margins required to get a centered origin.
If you need the centered frame before you invoke initWithFrame: then just use the above logic to compute the frame origin values.

Swift 5 #d11wtq's answer
let x = (parentView.bounds.width - subviewWidth) * 0.5
let y = (parentView.bounds.height - subviewHeight) * 0.5
let f = CGRect(x: x, y: y, width: subviewWidth, height: subviewHeight)
let subview = NSView(frame: f)
subview.autoresizingMask = [.minXMargin, .maxXMargin, .minYMargin, .maxYMargin ]

Objective - C in macOS Catalina , Version 10.15.3
more readable #d11wtq's answer
CGFloat x = (NSWidth(parentView.bounds) - subviewWidth) * 0.5;
CGFloat y = (NSHeight(parentView.bounds) - subviewHeight) * 0.5;
CGRect f = CGRectMake(x, y, subviewWidth, subviewHeight);
subview = [[NSView alloc] initWithFrame: f];
subview.autoresizingMask = NSViewMinXMargin | NSViewMaxXMargin | NSViewMinYMargin | NSViewMaxYMargin;

Try This.
in .h
#property (strong, nonatomic) IBOutlet UIView *CoinsPurchaseView;
in .m
self.CoinsPurchaseView.center = self.view.center;
[self.view addSubview:self.CoinsPurchaseView];

Related

Centering text in OS X Screensaver

I'm working on implementing a (very) simple screensaver using the Screensaver framework in OS X 10.10. Positioning on the center of the screen and displaying a two-line text works without problem, but setting the alignment to NSCenterTextAlignment somehow doesn't (the text is always displayed left-aligned).
- (void)animateOneFrame
{
// calculate font size based on screen
NSSize size = [self bounds].size;
CGFloat fontsize = size.height / 11;
// set text
NSMutableParagraphStyle *centredStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
[centredStyle setAlignment:NSCenterTextAlignment];
NSDictionary *textAttributes = [NSDictionary dictionaryWithObjectsAndKeys:
[NSFont fontWithName:#"Futura" size:fontsize], NSFontAttributeName,
[NSColor orangeColor], NSForegroundColorAttributeName,
centredStyle, NSParagraphStyleAttributeName,
nil];
NSString *theText = #"Simple text spanning\ntwo lines";
// position text on screen
NSRect boundingRect = [theText boundingRectWithSize:size options:0 attributes:textAttributes];
NSPoint point = NSMakePoint((size.width - boundingRect.size.width) / 2.0,
(size.height - boundingRect.size.height) / 2.0);
[theText drawAtPoint: point withAttributes: textAttributes];
}
Any pointers on how to solve this are appreciated.
PS: I know that I don't need to put everything into animateOneFrame but for the moment the goal is to get it working at all:-)

What is the best way to display a single-paged pdf as an image?

I would like to display in an NSView a single-paged PDF.
So far, I have two solutions but they both have downsides. Can anyone help me with any of these downsides?
First solution: with NSImage and NSImageView
NSString *path= [[NSBundle mainBundle] pathForResource:name ofType:#"pdf"];
NSImage * image = [[NSImage alloc] initWithContentsOfFile:path] ;
NSImageView * imageView = [[NSImageView alloc] init] ;
imageView.frame = NSMakeRect(0, 0, 2*image.size.width, 2*image.size.height) ;
imageView.image = image ;
imageView.imageScaling = NSImageScaleAxesIndependently ;
return imageView
Downsides:
the image is not anti-aliased
I don't understand why the factor 2 is needed. Why does my PDF is displayed smaller in an NSView than it is with the Finder?
Second solution: with PDFDocument and PDFView
NSString *path= [[NSBundle mainBundle] pathForResource:name ofType:#"pdf"];
NSURL *urlPDF = [NSURL fileURLWithPath:path] ;
PDFDocument * myPDFDocument = [[PDFDocument alloc] initWithURL:urlPDF] ;
PDFView *myPDFView = [[PDFView alloc] init] ;
myPDFView.document = myPDFDocument ;
PDFPage * firstPage = [myPDFDocument pageAtIndex:0] ;
NSRect myBounds = [firstPage boundsForBox:kPDFDisplayBoxMediaBox] ;
NSRect myNewBounds = NSMakeRect(0, 0, myBounds.size.width*2, myBounds.size.height*2+5) ;
myPDFView.frame = myNewBounds ;
myPDFView.autoScales = YES ;
return myPDFView ;
Downsides:
I am able to select the text of my pdf, I can zoom in or zoom out. But I would like my PDF document to be displayed as an image, without these possibilities
I don't understand why the factor 2 is needed. Why is my PDF displayed smaller in an NSView than it is with the Finder?
There are some margins around my image
I'm not seeing the problems you describe with NSImageView. I implemented a nib-based window and NSImageView. In my case I have an overlapping sibling view, so I turned CALayers turned on in the nib. I'm on 10.9.2. Sizing is normal (1x) and the text in my PDF is anti-aliased (sub-pixel I think, since I see colors when I blow it up). I do have scaling NONE - maybe scaling is preventing anti-aliased text?
Otherwise my guess is there's something different about your views or or PDF content. Try a simpler PDF and/or a nib-based view and if it works, you can look for differences.

Centering UIImage and UILabel together

I want to center my label's text while also considering a 20x20 image that should always be just to the left of the label. Specifically, I have three labels who's text could be anything, and I want a check mark to always appear just to the left of each label - this position will vary depending on the text length though.
My best guess is making the image a subview of the label and then indenting the text but that still will be somewhat inconsistent...
In iOS 6 you can do this with constraints and almost no code. But for iOS 5 using only springs and struts you need to manage the view frames your self. You will also need to call sizeToFit so the UILabel resizes it's self to the current text.
Here is some example code:
- (void)centerLabels
{
CGRect viewFrame = self.view.frame;
NSArray *allLabelsAndChecks = #[ #[self.label1, self.check1], #[self.label2, self.check2], #[self.label3, self.check3] ];
for (NSArray *labelAndCheck in allLabelsAndChecks) {
UILabel *label = labelAndCheck[0];
UIImageView *check = labelAndCheck[1];
[label sizeToFit];
CGRect labelFrame = label.frame;
CGRect checkFrame = check.frame;
CGFloat maxWidth = viewFrame.size.width - (leftMarginText + rightMargin);
if (labelFrame.size.width > maxWidth) {
labelFrame.origin.x = leftMarginText;
labelFrame.size.width = maxWidth;
checkFrame.origin.x = leftMarginImage;
} else {
CGFloat slideRight = (maxWidth - labelFrame.size.width) / 2.0;
labelFrame.origin.x = leftMarginText + slideRight;
checkFrame.origin.x = leftMarginImage + slideRight;
}
label.frame = labelFrame;
check.frame = checkFrame;
}
}
The commplete demo project can be found here: https://github.com/GayleDDS/TestCenteredLabel.git

setRadiusCorner just at the bottom of the view

i have a view with rounded corners.
I want to make it rounded just at the two bottom corners of the view.
How can i do that?
This is my code that makes the rounded corners for the view:
CALayer *myLayer = moreInfoView.layer;
[myLayer setCornerRadius:20.0];
[view.layer setMasksToBounds:YES];
Is it possible?
Thanks!
Round only the UIRectCornerBottomLeft and UIRectCornerBottomRight corners:
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:yourView.bounds
byRoundingCorners:(UIRectCornerBottomLeft | UIRectCornerBottomRight)
cornerRadii:CGSizeMake(5.0, 5.0)];
CAShapeLayer *layer = [CAShapeLayer layer];
layer.frame = yourView.bounds;
layer.path = path.CGPath;
yourView.layer.mask = layer;
Don't think you can do that with the CALayer with ease - you could subclass NSView and make a simple redraw routine. May be easiest to use http://www.cocoadev.com/index.pl?RoundedRectangles as a starting point (after removing the two bezier Paths you do not want to have rounded) and then do a simple:
- (void)drawRect:(CGRect)rect
{
self.layer.masksToBounds = NO;
self.layer.shadowColor = [[UIColor xxxx] CGColor];
self.layer.shadowOffset = CGSizeMake(0,2);
self.layer.shadowRadius = ...
self.layer.shadowOpacity = ...
UIBezierPath *path = [UIBezierPath bezierPathWithPartiallyRoundedRect:rect
byRoundingCorners:UIRectCornerAllCorners cornerRadii:CGSizeMake(20, 20)];
[[UIColor blackColor] setFill or Stroke];
[path stroke or fill];
}
In above link - the second simplified example makes your live easier - as you can simply make two of them 'path lineToPoint: ..'.

removing/adding CALayers for GPU optimization

I have a layer backed view, I am trying to add subLayers roughly sized around 300 X 270 (in pixels) to it.
The sublayers' count may reach 1000 to 2000, not to mention each sublayer is again scalable to roughly 4280 X 1500 or more for starters.
So the problem is obviously that of a GPU constraint.
After adding around 100 subLayers sized 300 X 270 , there is a warning image is too large for GPU, ignoring and that is messing with the layer display.
The solution for such a problem (from some mailing lists) was to use CATiledLayer, but I can't make use of the tiledLayer due to the complex requirement of the subLayers' display.
Is there a possibility of removing the subLayers which don't fall under VisibleRect of the view?
I tried to removeFromSuperlayer and then add it whenever required, there's always a crash when I try to add the subLayer back.
How can I do this?
I am adding sublayer twice (I need to change it) but for now just for the gist of the code:
-(IBAction)addLayer:(id)sender
{
Layer *l = [[Layer alloc] init];
CALayer *layer = [l page];
[contentArray addObject:page];
[drawLayer addSublayer:layer];
[self layout];
}
-(void)layout
{
NSEnumerator *pageEnumr = [contentArray objectEnumerator];
float widthMargin = [self frame].size.width;
CGRect rect;
float zoom = [self zoomFactor];
while(obj = [contentEnmr nextObject] )
{
[obj setZoomFactor:zoom];
CALayer *pg =(CALayer *)[obj page] ;
rect = pg.bounds;
if ( x + pg.bounds.size.width > widthMargin )
{
x = xOffset;
y += rect.size.height + spacing ;
}
rect.origin = CGPointMake(x,y);
[obj changeBounds];
NSRect VisibleRect = [self visibleRect];
NSRect result = NSIntersectionRect(VisibleRect,NSRectFromCGRect( rect));
if( NSEqualRects (result ,NSZeroRect) )
{
[pg removeFromSuperlayer];
}else
{
[drawLayer addSublayer:pg];
[pg setFrame:rect];
[pg setNeedsDisplay];
}
x += ( rect.size.width + spacing);
}
NSRect viewRect = [self frame];
if(viewRect.size.height < ( y + rect.size.height + spacing ) )
viewRect.size.height = ( y + rect.size.height + spacing) ;
[self setFrameSize: viewRect.size];
}
#interface Layer : NSObject {
CALayer *page;
}
#property (retain) CALayer *page;
Have a look at the PhotoScroller application included as part of the WWDC conference. It demonstrates how to zoom and scroll through a very large image by loading only portions of that image that are currently visible.
Also check out this discussion.
You'll need to do what NSTableView and UITableView do, and manage the addition / removal of layers yourself whenever the visible rect changes. Subscribe to the boundsDidChange noitification of the enclosing scroll view's clip view (I'm assuming that the reason some of the layer is offscreen is that it's enclosed in a scroll view):
- (void) viewDidMoveToSuperview
{
NSClipView* clipView = [[self enclosingScrollView] contentView];
[clipView setPostsBoundsChangedNotifications:YES];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(clipViewBoundsDidChange:)
name:NSViewBoundsDidChangeNotification
object:clipView];
}
and then write a clipViewBoundsDidChange: method that adds and removes sublayers as needed. You may also want to cache and reuse invalidated layers to cut down on allocations. Take a look at the way UITableView and NSTableView interact with their dataSource object for some ideas about how to design the interface for this.
CATiledLayer solves this problem the content of a layer --- ie, whatever you set its contents property or draw into its graphics context directly. It won't do this for sublayers, in fact I think you're advised not to add sublayers to a CATiledLayer at all, as this interferes with its drawing behaviour.

Resources