I want to know is there any method to render a view to image? just like a screenshot, but I can specify any view to that method. I know how to do it in ios. Saving view content, but how can I do it in mac os ?
There are several ways, but non of them works perfect.
Many problems are connected with Layer Backed views.
if you don't have layer backing or hosting views you can use this code:
NSData* data = [mainView dataWithPDFInsideRect:[mainView bounds]];
NSImage *img = [[NSImage alloc] initWithData:data];
if you work with layer based views:
till 10.8 the best way for me was
NSSize mySize = mapImage.bounds.size;
NSSize imgSize = NSMakeSize( mySize.width, mySize.height );
NSBitmapImageRep *bir = [mapImage bitmapImageRepForCachingDisplayInRect:[mapImage bounds]];
[bir setSize:imgSize];
[mapImage cacheDisplayInRect:[mapImage bounds] toBitmapImageRep:bir];
caheImg = [[NSImage alloc]initWithSize:imgSize];
[caheImg addRepresentation:bir];
but in 10.8 bitmapImageRepForCachingDisplayInRect stopped working with Layer Backed views.
there is an option to make a screenshot of your screen and cut out your view:
+ (NSImage*) screenCacheImageForView:(NSView*)aView
{
NSRect originRect = [aView convertRect:[aView bounds] toView:[[aView window] contentView]];
NSRect rect = originRect;
rect.origin.y = 0;
rect.origin.x += [aView window].frame.origin.x;
rect.origin.y += [[aView window] screen].frame.size.height - [aView window].frame.origin.y - [aView window].frame.size.height;
rect.origin.y += [aView window].frame.size.height - originRect.origin.y - originRect.size.height;
CGImageRef cgimg = CGWindowListCreateImage(rect,
kCGWindowListOptionIncludingWindow,
(CGWindowID)[[aView window] windowNumber],
kCGWindowImageDefault);
return [[NSImage alloc] initWithCGImage:cgimg size:[aView bounds].size];
}
The answer from #Remizorr works in ObjectiveC like explained even under
macOS High Sierra 10.13.4. - Xcode 9.2.
It also works for layerBacked view (wantsLayer = true).
Here is the updated Swift version :
extension NSView {
func snapshotImage() -> NSImage? {
guard let window = window,
let screen = window.screen,
let contentView = window.contentView else { return nil }
let originRect = self.convert(self.bounds, to:contentView)
var rect = originRect
rect.origin.x += window.frame.origin.x
rect.origin.y = 0
rect.origin.y += screen.frame.size.height - window.frame.origin.y - window.frame.size.height
rect.origin.y += window.frame.size.height - originRect.origin.y - originRect.size.height
guard window.windowNumber > 0 else { return nil }
guard let cgImage = CGWindowListCreateImage(rect, .optionIncludingWindow, CGWindowID(window.windowNumber), CGWindowImageOption.bestResolution) else { return nil }
return NSImage(cgImage: cgImage, size: self.bounds.size)
}
}
NOTE: This works even for a WKWebView and it does take a snapshot of a WKWebView video playing (which WKWebViews takeSnapshot does not) ;).
I'm using this code in my app, which works great.
However, if I drag the app to a secondary monitor the screen capture is thrown out and I get the wrong rect captured.
The following code fixes that issue (I've also removed some redundant maths from the calculation where a field was being subtracted and then added) in case anyone comes across this in the future:
NSRect originRect = [aView convertRect:[aView bounds] toView:[[aView window] contentView]];
NSArray *screens = [NSScreen screens];
NSScreen *primaryScreen = [screens objectAtIndex:0];
NSRect rect = originRect;
rect.origin.y = 0;
rect.origin.x += [aView window].frame.origin.x;
rect.origin.y = primaryScreen.frame.size.height - [aView window].frame.origin.y - originRect.origin.y - originRect.size.height;
Related
good morning,
i have a problem when i tried to pass to a full screen by clicking on the zoom button.In fact when i tapped the button displays a video and a toolbar that allows to move in the video. it's seems worked perfectly but i discovered that when I move through the slider to a level almost 60% I can not move forward or backward.
i think that the problem is due to layout setting.
this the code :
- (void)changeFullScreen:(BOOL)isFullScreen index:(int)index{
if (isFullScreen == YES) {
CGAffineTransform affineTransforme = CGAffineTransformMakeRotation(M_PI_2);
//CGFloat scale = [UIScreen mainScreen].bounds.size.height / [UIScreen mainScreen].bounds.size.width;
//affineTransforme = CGAffineTransformScale(affineTransforme,scale , scale);
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:index inSection:0];
EMPOIVideoCell *cell = [poiTableView cellForRowAtIndexPath:indexPath];
tmpVideoPlayerview = [cell.containerView viewWithTag:101];
//tmpVideoPlayerview.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
tmpVideoPlayerview.transform = affineTransforme; //CGAffineTransformTranslate(affineTransforme, 190, 0);
fullScreenView = [[UIViewController alloc] init];
tmpVideoPlayerview.frame = CGRectMake(-248, 0, fullScreenView.view.bounds.size.height , fullScreenView.view.bounds.size.width);
tmpVideoPlayerview.videoContainer.frame = tmpVideoPlayerview.frame;
tmpVideoPlayerview.videoContainer.layer.frame = tmpVideoPlayerview.frame;
playerLayer = [AVPlayerLayer playerLayerWithPlayer:tmpVideoPlayerview.videoPlayer];
playerLayer.frame = tmpVideoPlayerview.videoContainer.bounds;
[tmpVideoPlayerview.avPlayerLayer removeFromSuperlayer];
[tmpVideoPlayerview.videoContainer.layer addSublayer:playerLayer];
tmpVideoPlayerview.contentMode = UIViewContentModeScaleToFill;
//[tmpVideoPlayerview updateConstraints];
[tmpVideoPlayerview setNeedsLayout] ;
[tmpVideoPlayerview layoutIfNeeded];
fullScreenView.view.clipsToBounds = YES;
[fullScreenView.view addSubview:tmpVideoPlayerview];
[self addChildViewController:fullScreenView];
[self.view addSubview:fullScreenView.view];
//[fullScreenView.view updateConstraints];
//[fullScreenView.view layoutIfNeeded];
[[UIApplication sharedApplication] setStatusBarHidden:YES];
}
photo to explain the situation
I'm wondering what's the best practice to resize a window when Toolbar changes.
I'm trying to get this effect (animated) when a Toolbar selected option changes.
Any ideas?
Thank you for your help! :)
Here's my commonly used method:
After clicking the new toolbar item, firstly get the frame size of the new subview to be added, then change window's frame with animation.
Demo (just change height, but you can add support to change width):
- (void)switchToTabView:(NSView *)settingView withAnimation:(BOOL)animation
{
NSView *windowView = self.window.contentView;
for (NSView *view in windowView.subviews) {
[view removeFromSuperview];
}
CGFloat oldHeight = windowView.frame.size.height;
[windowView addSubview:settingView];
CGFloat newHeight = settingView.frame.size.height;
CGFloat delta = newHeight - oldHeight;
NSPoint origin = settingView.frame.origin;
origin.y -= delta;
[settingView setFrameOrigin:origin];
NSRect frame = self.window.frame;
frame.size.height += delta;
frame.origin.y -= delta;
[self.window setFrame:frame display:YES animate:animation];
}
#GoKu Your answer gave me a hint how to solve it. I just added a new property for my window called "win".
Here's my solution:
- (void)updateView:(int)tag{
[[_ourViewController view] removeFromSuperview];
if (tag==0) {
self.ourViewController = [[UserView alloc] initWithNibName:#"UserView" bundle:nil];
} else if (tag==1){
self.ourViewController = [[ComputerView alloc] initWithNibName:#"ComputerView" bundle:nil];
}
NSView *newView = _ourViewController.view;
NSRect windowRect = _win.frame;
NSRect currentViewRect = newView.frame;
windowRect.origin.y = windowRect.origin.y + (windowRect.size.height - currentViewRect.size.height);
windowRect.size.height = currentViewRect.size.height;
windowRect.size.width = currentViewRect.size.width;
[self.win setContentView:newView];
[self.win setFrame:windowRect display:YES animate:YES];
}
Thanx!
For some strange reason, my UITextView's text appears cropped when the scrollofset is set.
Here's what it looks like:
This happens after doing:
textview.contentInset = UIEdgeInsetsZero;
[textview setContentOffset:CGPointMake(0, startypos + offset_yshift) animated:NO];
I tried manually setting contentSize.height, but that introduced another strange behavior, where the content offset seems to be ignored..
''Edit'': This is the code used to instantiate the textfield:
CGRect myImageRect = CGRectMake(-50.0f, -50.0f, 40.0f, 40.0f);
textview = [[UITextView alloc] initWithFrame: myImageRect];
textview.autocapitalizationType = UITextAutocapitalizationTypeNone;
[textview setScrollEnabled:YES];
textview.hidden = YES;
textview.contentInset = UIEdgeInsetsZero;
textview.opaque = NO;
textview.backgroundColor = [UIColor clearColor];
textview.textColor = [UIColor colorWithWhite:1 alpha:1];
textview.textAlignment = NSTextAlignmentCenter;
textview.frame = CGRectMake(0, 0, 250, 30);
textview.scrollEnabled = NO;
And the "update" code that checks the content positioning every frame:
// setting the actual size here
UITextPosition * pos = [textview positionFromPosition: textview.endOfDocument offset:nil];
CGRect therect = [textview caretRectForPosition:pos];
CGRect frame = textview.frame;
if([textview.text length] == 0){
frame.size.height = 30;
} else {
frame.size.height = therect.origin.y + therect.size.height;
}
// and here, we're changing the frame variable's height to max to 50
if(frame.size.height > 50){
frame.size.height = 50;
}
frame.size.width = desiredwidth; // some other variable
textview.frame = frame;
/*
... snip, unrelated code ...
*/
// later on
textview.contentInset = UIEdgeInsetsZero;
[textview setContentOffset:CGPointMake(0, startypos + offset_yshift) animated:NO];
As you can imagine, the setContentOffset bit there is what's causing the issue.
What is going on?
please try this from prev post
CGPoint offset = CGPointMake(0, self.textView.contentSize.height - self.textView.frame.size.height);
[self.textView setContentOffset: CGPointMake(0,0) animated:NO];
or
[self.textView setContentOffset:bottomOffset animated:YES];
[post]: UITextView contentOffset on iOS 7 "prev post"\
I ran into a very similar issue in iOS7 with UITextField - looks almost exactly the same. Mine had to do with content insets as well.
The solution (for me) - was to eliminate the UITextField from nib/storyboard and instantiate it and add it to the view purely from code. Behavior worked as expected afterwards.
I don't know what was up other than it probably being a iOS7 / XCode 5 bug... Curious to know if that solves it for you too.
My thougts would be, to adjust the size of the UITextView based on its content.
Like asked here: how-do-i-size-a-uitextview-to-its-content
Code taken from jhibberd ´s answer
- (void)textViewDidChange::(UITextView *)textView
{
CGFloat fixedWidth = textView.frame.size.width;
CGSize newSize = [textView sizeThatFits:CGSizeMake(fixedWidth, MAXFLOAT)];
CGRect newFrame = textView.frame;
newFrame.size = CGSizeMake(fmaxf(newSize.width, fixedWidth), newSize.height);
textView.frame = newFrame;
}
I have an app that resizes itself for different views using:
NSSize currentSize = [[box contentView] frame].size;
NSSize newSize = [v frame].size;
float deltaWidth = newSize.width - currentSize.width;
float deltaHeight = newSize.height - currentSize.height;
NSRect windowFrame = [w frame];
windowFrame.size.height += deltaHeight;
windowFrame.origin.y -= deltaHeight;
windowFrame.size.width += deltaWidth;
[box setContentView: nil];
[w setFrame: windowFrame
display: YES
animate: YES];
[box setContentView: v];
When you change the view, the window grows/shrinks based on the upper-left corner of the app. The app always starts on the first view, no matter which view the user was in when they quit, because this is a summary view. I also want it to be restorable so the last-used document is open on launch.
The problem: since most views are taller than the summary view, changing to one pushes the bottom left corner of the window farther down the screen. Now quit the app and relaunch, and the app positions the window to where that bottom left corner was previously. I know this is because that's where Cocoa puts the origin, but it makes more sense for the user to have the window restart with the same top left corner, otherwise the app is shifting down the screen each time it's opened.
I tried observing NSWindowWillCloseNotification and calling the above method again to reset the app to the summary view just before closing, but even though the code works, the window still starts in the wrong position - I'm guessing Cocoa sets an app's restorable defaults before the notification is sent.
It's been done before: System Preferences does it - it auto-resizes for views, dictates what your starting view is, but you can move the window and next time you open it, it'll be in the new position based on the top left corner. Anyone have an idea how to emulate that?
Edit: Document.xib has a small window with a popup button and a box. The methods below change the views and resize the window to fit those views. Everything works fine while the app is open, the window shifting is only an issue when the app is closed and re-opened with an active document (ie. using Lion's restore).
============================
| Jump to:___ | The "===" is the top and bottom edge of the window
| | The "___" is the pop-up menu for selecting the view
| -------------------------- | The dashed box on the inside is "box", which holds the views
|| ||
|| ||
|| ||
|| ||
|| ||
|| ||
| -------------------------- |
============================
- (void)windowControllerDidLoadNib:(NSWindowController *)aController
{
[super windowControllerDidLoadNib:aController];
//Note that init creates the array viewControllers
NSMenu *menu = [viewMenu menu];
NSUInteger i, itemCount;
itemCount = [viewControllers count];
for (i = 0; i < itemCount; i++) {
NSViewController *vc = [viewControllers objectAtIndex:i];
NSMenuItem *mi = [[NSMenuItem alloc] initWithTitle: [vc title] action: #selector(changeViewController:) keyEquivalent:#""];
[mi setTag: i];
[menu addItem: mi];
}
[self displayViewController: [viewControllers objectAtIndex: 0]];
[viewMenu selectItemAtIndex: 0];
}
- (void) displayViewController: (ManagingViewController *) vc {
//End editing
NSWindow *w = [box window];
BOOL ended = [w makeFirstResponder:w];
if (!ended) {
NSBeep();
return;
}
//Put view in box
NSView *v = [vc view];
NSSize currentSize = [[box contentView] frame].size;
NSSize newSize = [v frame].size;
float deltaWidth = newSize.width - currentSize.width;
float deltaHeight = newSize.height - currentSize.height;
NSRect windowFrame = [w frame];
windowFrame.size.height += deltaHeight;
windowFrame.origin.y -= deltaHeight;
windowFrame.size.width += deltaWidth;
[box setContentView: nil];
[w setFrame: windowFrame display: YES animate: YES];
[box setContentView: v];
}
- (IBAction)changeViewController:(id)sender {
NSUInteger i = [sender tag];
ManagingViewController *vc = [viewControllers objectAtIndex: i];
[self displayViewController: vc];
}
I tried putting the first block of suggested code from #trudyscousin just below the for loop in -windowControllerDidLoadNib: and that anchored the window correctly, but when the code reaches -displayViewController: it sets currentSize = (6,6). I'm not sure if that's because the box is empty in IB. It then adds the difference between (6,6) and the size of the SummaryView to the size of the window when the app last quit, making it huge.
I then tried using NSUserDefaults by adding two lines to -changeViewController:
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
[userDefaults setInteger: i forKey: #"lastViewController"];
and in -windowControllerDidLoadNib:
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
if ([userDefaults integerForKey: #"lastViewController"]) {
NSInteger i = [userDefaults integerForKey: #"lastViewController"];
ManagingViewController *vc = [viewControllers objectAtIndex: i];
NSView *v = [vc view];
NSSize currentSize = [v frame].size;
[box setFrameSize: currentSize];
[self displayViewController: vc];
[viewMenu selectItemAtIndex: i];
} else {
[self displayViewController: [viewControllers objectAtIndex: 0]];
[viewMenu selectItemAtIndex: 0];
}
This works, but forces the app to launch with the last used view, which wouldn't be so bad except that it will even do that when a new document is opened/created. If I get rid of the else (i.e., always run those last 2 lines), I'm back to where I started - the app launches being shifted vertically down the screen. It looks like you can't set the window's origin at launch, or maybe it reloads its own defaults at a later point.
The last thing I did was go back to my starting code (not using the mask code provided by #trudyscousin) but at the bottom of -windowControllerDidLoadNib: I added:
[self performSelector: #selector(adjustOrigin) withObject: nil afterDelay:0.0f];
which calls:
- (void) adjustOrigin {
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
if ([userDefaults integerForKey: #"lastViewController"]) {
NSInteger i = [userDefaults integerForKey: #"lastViewController"];
ManagingViewController *vc = [viewControllers objectAtIndex: i];
NSView *v = [vc view];
NSSize previousSize = [v frame].size;
NSSize currentSize = [[[viewControllers objectAtIndex: 0] view] frame].size;
CGFloat delta = previousSize.height - currentSize.height;
NSRect windowFrame = [[box window] frame];
windowFrame.origin.y += delta;
[[box window] setFrame: windowFrame display:YES];
}
}
That actually does work, but if multiple things are running, the lag is enough that you can see the window make the "jump" to its new origin, which is a little inelegant.
Your code appears to be sound, but the real problem may be your document xib.
If your document xib is set up for Auto Layout, turn that off. You can do that by selecting the file in Xcode. In the File inspector, turn off the "Use Auto Layout" check box.
Once you've done that, select your popup button. In the Size inspector, anchor the button at the top and left side. Select your box. In the Size inspector, anchor the box on all four sides, and make sure it expands horizontally and vertically.
To recap from my earlier answer, here's your -windowControllerDidLoadNib: method with the changes I had suggested earlier:
- (void)windowControllerDidLoadNib:(NSWindowController *)aController
{
[super windowControllerDidLoadNib:aController];
//Note that init creates the array viewControllers
NSMenu *menu = [viewMenu menu];
NSUInteger i, itemCount;
itemCount = [viewControllers count];
for (i = 0; i < itemCount; i++) {
NSViewController *vc = [viewControllers objectAtIndex:i];
NSMenuItem *mi = [[NSMenuItem alloc] initWithTitle: [vc title] action: #selector(changeViewController:) keyEquivalent:#""];
[mi setTag: i];
[menu addItem: mi];
}
// ---
NSWindow *myWindow = [[[self windowControllers] objectAtIndex:0] window];
NSUInteger styleMask = [myWindow styleMask];
styleMask ^= NSResizableWindowMask;
[myWindow setStyleMask:styleMask];
if ([myWindow setFrameUsingName:#"windowFrameAutosaveName"] == NO)
{
[myWindow center];
}
(void) [myWindow setFrameAutosaveName:#"windowFrameAutosaveName"];
styleMask &= ~NSResizableWindowMask;
[myWindow setStyleMask:styleMask];
// ---
[self displayViewController:[viewControllers objectAtIndex:0]];
[viewMenu selectItemAtIndex:0];
}
I removed the older comments but now indicate where the additional code is. With this, you shouldn't have to bother with maintaining your own defaults for the document, and you don't have to bother with adjusting the origin.
Put these two things together, and you (largely) have your fix.
Yes, I needed to qualify that somewhat. Yours is a document-based application, and so you're going to have to either come up with either a unique name under which to save your document's window location in your user defaults, or perhaps a means of somehow storing that information with the document itself.
As always, good luck to you in your endeavors.
I have one view controller in which I have added UIScrollView & UIImageView programatically. I have added UIPichGestureRecognizer to the UIImageView. My UIImageView is added to UIScrollView as a subview.
My problem is when I try to pinch the image , it zoom in. But when I release the touches from screen it again come to its default size. I can not find the error in code. Please help me.
Below is my code
- (void)createUserInterface {
scrollViewForImage = [[UIScrollView alloc]initWithFrame:CGRectMake(20.0f, 60.0f, 280.0f, 200.0f)];
scrollViewForImage.userInteractionEnabled = YES;
scrollViewForImage.multipleTouchEnabled = YES;
scrollViewForImage.backgroundColor = [UIColor redColor];
scrollViewForImage.autoresizesSubviews = YES;
scrollViewForImage.maximumZoomScale = 1;
scrollViewForImage.minimumZoomScale = .50;
scrollViewForImage.clipsToBounds = YES;
scrollViewForImage.delegate = self;
scrollViewForImage.bouncesZoom = YES;
scrollViewForImage.contentMode = UIViewContentModeScaleToFill;
[self.contentView addSubview:scrollViewForImage];
imageView = [[UIImageView alloc]initWithFrame:CGRectMake(0.0f, 0.0f, 280.0f, 200.0f)];
[imageView setBackgroundColor:[UIColor clearColor]];
imageView.userInteractionEnabled = YES;
imageView.multipleTouchEnabled = YES;
UIPinchGestureRecognizer *pinchRecognizer = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:#selector(pinch:)];
[pinchRecognizer setDelegate:self];
[imageView addGestureRecognizer:pinchRecognizer];
//[self.contentView addSubview:imageView];
[self.scrollViewForImage addSubview:imageView];
scrollViewForImage.contentSize = CGSizeMake(imageView.frame.size.width , imageView.frame.size.height);
}
-(UIView *) viewForZoomingInScrollView:(UIScrollView *)inScroll {
return imageView;
}
-(void)pinch:(id)sender {
[self.view bringSubviewToFront:[(UIPinchGestureRecognizer*)sender view]];
if([(UIPinchGestureRecognizer*)sender state] == UIGestureRecognizerStateEnded) {
lastScale = 1.0;
return;
}
CGFloat scale = 1.0 - (lastScale - [(UIPinchGestureRecognizer*)sender scale]);
CGAffineTransform currentTransform = [(UIPinchGestureRecognizer*)sender view].transform;
CGAffineTransform newTransform = CGAffineTransformScale(currentTransform, scale, scale);
[[(UIPinchGestureRecognizer*)sender view] setTransform:newTransform];
lastScale = [(UIPinchGestureRecognizer*)sender scale];
}
From what I can see the problem lies here:
scrollViewForImage.maximumZoomScale = 1;
You are setting the maximum zoom scale of the image to 1 x its full size. This means once you finish pinching the image, it will scale back to 1 x its size.
If you want to be able to zoom the image to a larger size, try settings this value to be higher than 1. e.g.
scrollViewForImage.maximumZoomScale = 3;