How to resize WebView according to its content? - cocoa

I want to set simple html contents within a web view and then resize it according to its content.
To set simple html contents within web view I used this code and it is working fine:
[[myWebView mainFrame] loadHTMLString:webViewContents baseURL:baseURLFramed];
Right now, if content is more than its actual size then it appears in web view showing both vertical and horizontal scroller in it. I want to set some default width and manage height according to its content in a way so that neither horizontal nor vertical scroller appears.
Can anyone suggest me some solution for it?
Thanks,
Miraaj

You can register as the WebView's frame load delegate, and when the page loads you can ask the main frame's document view for its size and resize the WebView. Make sure you turn off scrollbars on the frame or you will have problems.
Be aware that certain pages can be very large, when I tested daringfireball.net it came in at 17612 points high, which is obviously too large to display on-screen.
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
//set ourselves as the frame load delegate so we know when the window loads
[webView setFrameLoadDelegate:self];
//turn off scrollbars in the frame
[[[webView mainFrame] frameView] setAllowsScrolling:NO];
//load the page
NSURLRequest* request = [NSURLRequest requestWithURL:[NSURL URLWithString:#"http://daringfireball.net"]];
[[webView mainFrame] loadRequest:request];
}
//called when the frame finishes loading
- (void)webView:(WebView *)sender didFinishLoadForFrame:(WebFrame *)webFrame
{
if([webFrame isEqual:[webView mainFrame]])
{
//get the rect for the rendered frame
NSRect webFrameRect = [[[webFrame frameView] documentView] frame];
//get the rect of the current webview
NSRect webViewRect = [webView frame];
//calculate the new frame
NSRect newWebViewRect = NSMakeRect(webViewRect.origin.x,
webViewRect.origin.y - (NSHeight(webFrameRect) - NSHeight(webViewRect)),
NSWidth(webViewRect),
NSHeight(webFrameRect));
//set the frame
[webView setFrame:newWebViewRect];
//NSLog(#"The dimensions of the page are: %#",NSStringFromRect(webFrameRect));
}
}

Another alternative is to ask the DOM for the height of your content.
- (void)webView:(WebView *)sender didFinishLoadForFrame:(WebFrame *)frame
{
NSString* heightOutput = [sender stringByEvaluatingJavaScriptFromString:#"document.getElementById(\"foo\").scrollHeight;"];
NSInteger height = [heightOutput integerValue];
[[self splitView] setPosition:height + 10 ofDividerAtIndex:0];
}
The key point here is to place your content inside of a named element so getElementById can be used. My implementation used a simple <div id="foo"> element.
In my particular case, my WebView was inside of a NSSplitView, so first resetting the height of the WebView to something small was causing a flicker.
I've got a sample project here demonstrating it.

Related

Efficiently handling UIImage loading in UICollectionView

In my app the user can take a photo of their pet, or whatever, and that will show in the collection view as a circular picture. Using cellForItemAtIndexPath to check for the existence of a user generated photo (which is stored in the Documents directory as a png file), if not display a default photo.
Issues:
- When Scrolling the UICollectionView the image load halts the
smoothness of the scroll UI as the picture is going to appear on
screen.
- If you scroll the picture off the screen many times the
app crashes due to memory pressure.
QUESTION: Is there an efficient way of loading and keeping the images in memory without having to keep loading the image after the first time it is displayed?
I thought rasterizing the images would help, but it seems the UI scrolling and memory issues persist.
Here is the code:
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifierPortrait = #"allPetCellsPortrait";
AllPetsCell *cell = (AllPetsCell *)[collectionView dequeueReusableCellWithReuseIdentifier:CellIdentifierPortrait forIndexPath:indexPath];
CGRect rect = CGRectMake(cell.petView.center.x, cell.petView.center.y, cell.petView.frame.size.width, cell.petView.frame.size.height);
UIImageView *iv = [[UIImageView alloc] initWithFrame:rect];
//set real image here
NSString *petNameImage = [NSString stringWithFormat:#"%#.png", self.petNames[indexPath.section][indexPath.item]];
NSString *filePath = [_ad documentsPathForFileName:petNameImage];
BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:filePath];
iv.image = nil;
if (fileExists){
[_allPetsCollectionView performBatchUpdates:^{
if (iv && fileExists) {
iv.image = [UIImage imageWithContentsOfFile:filePath];
}
} completion:^(BOOL finished) {
//after
}];
//loadedImage = YES;
[collectionView layoutIfNeeded];
}//file exists
else{
iv.image = [UIImage imageNamed:[NSString stringWithFormat:#"%#", self.contents[indexPath.section][indexPath.item]]];
[cell.petNameLabel setTextColor:[UIColor darkGrayColor]];//be aware of color over pictures
shadow.shadowColor = [UIColor whiteColor];
}
cell.petView.clipsToBounds = YES;
UIColor *color = MediumPastelGreenColor;
if ([[NSString stringWithFormat:#"%#", self.contents[indexPath.section][indexPath.item]] isEqualToString:#"addpet.png"]) {
color = MediumLightSkyBlueColor;
}
[cell.petView setBounds:rect forBorderColor:color];
[cell.petView addSubview:iv];
if (![[NSString stringWithFormat:#"%#", self.contents[indexPath.section][indexPath.item]] isEqualToString:#"addpet.png"])
[iv addSubview:cell.petNameLabel];
//TODO: optimize the cell by rasterizing it?
if ([_contents count] > 4) {
cell.layer.shouldRasterize = YES;
}
return cell;
}
Here is a screen shot:
Thanks!
Assigning an image to the collection view cell's imageView doesn't cost you any performance issues at all..
There are 2 problems here.
You are fetching the contents (datasource for the collection view) from within the collection view's delegate method. This is causing the delay.
You are adding the imageView holding the image again and again. Thus, each time the cell is displayed, a new imageView holding a newly fetched data is being added as the subView to the reused cell (As the new imageView covers the old one as both have the same frame, you wouldn't notice. And at the sametime this eats up memory). This is causing the memory pressure crash.
SOLUTION
FOR IMPROVING PERFORMANCE
Fetch the image contents from your model class(if you are using one), or from you viewController's viewDidLoad() (You should never fetch datasource from within the collection view's delegate method). Perform the fetching process in the background queue as it wouldn't affect your UI in any manner.
Store the contents onto an array and fetch the contents only from the array to populate your collection view cells.
FOR AVOIDING MEMORY CRASH
As long as you are not using a custom collection view cell, its better to remove all the subviews from the collectionViewCell's content view each time the cell is dequeued.
I hope this solves your problem :)
I recently discovered NSCache which is a dictionary type object that I can store my *already loaded image*s into and check to see if they are loaded with the -cellForItemAtIndexPath method.
I changed the above code to accommodate for this also by using a custom dispatch queue that would be sent to the main queue. This still causes a slight pause bump in the scrolling when the initial image is loaded but after that it is read from the image cache. Not totally optimal yet but I think I am headed in the right direction.
here is the code:
if (fileExists){
//DONE: implement NSCACHE and dispatch queue
const char *petImageQ = "pet image";
dispatch_queue_t petImageQueue = dispatch_queue_create( petImageQ, NULL);
//check to see if image exsists in cache - if not add it.
if ([_petImagesCache objectForKey:petNameImage] == nil) {
dispatch_async(petImageQueue, ^{
dispatch_async(dispatch_get_main_queue(), ^{
[collectionView performBatchUpdates:^{
iv.image = [UIImage imageWithContentsOfFile:filePath];
} completion:^(BOOL finished) {
//add to cache
[_petImagesCache setObject:iv.image forKey:petNameImage];
}];
});
});
}
else{
//load from cache
iv.image = [_petImagesCache objectForKey:petNameImage];
}
I hope this helps. Please add more to this for improving it!

Insertion Point in NSTextField missing

I have an NSTextField in a container:
[textField setFrameOrigin:NSMakePoint(0, -t.frame.size.height)];
content = [[NSView alloc] init];
[content setWantLayer:YES]
content.layer=[CALayer layer];
[content addSubview:textField];
[content scaleUnitSquareToSize:NSMakeSize(1, -1)];
content.frame=textField.frame;
content.layer.backgroundColor=textBGColor.CGColor;
The container itself is located in a view with
[view scaleUnitSquareToSize:NSMakeSize(1, -1)];
This is all for obtaining a top left origin for the TextField and it works great, the only problem consist in the InsertionPoint not drawing (at least not in the visible frame).
I presume the InsertionPoint is either not Scaled or translated with the TextField. Other possibility is that InsertionPoint can't be drawn in a layer backed View.
Is there a way to display the InsertionPoint cursor ?
EDIT
After trying all the possibilities out, it seems the InsertionPoint (and the focusRing) are not drawing because of its frame being positioned out of the superviews bounds and its dirtyDrawRect. Is there a way to remove the clipping of an NSView ? I need to be able to place my TextField on every absolute position possible.
I found a way through: implementing the drawing myself.
1) giving a custom TextView as Editor for the window.
- (id)windowWillReturnFieldEditor:(NSWindow *)sender toObject:(id)anObject
{
if (!myCustomFieldEditor) {
myCustomFieldEditor = [[TextView alloc] init];
[myCustomFieldEditor setFieldEditor:YES];
}
return myCustomFieldEditor;
}
2) Overiding the drawInsertionPoint method in the custom TextView class.
-(void)drawInsertionPointInRect:(NSRect)rect color:(NSColor *)color turnedOn:(BOOL)flag{
[color set];
NSRectFill(rect);
[super drawInsertionPointInRect:rect color:color turnedOn:flag];
}
For insertion point just make your textfield to first responder.
[myTextField becomeFirstResponder];

Two Finger Drag with IKImageView and NSScrollView in Mountain Lion

I have a Mac App that's been in the app store for a year or so now. It was first published with target SDK 10.7, Lion. Upon the update to Mountain Lion it no longer works.
The application displays large images in an IKImageView which is embedded in an NSScrollView. The purpose of putting it into a scrollview was to get two finger dragging working, rather than the user having to click to drag. Using ScrollViewWorkaround by Nicholas Riley, I was able to use two finger scrolling to show the clipped content after the user had zoomed in. Just like you see in the Preview app.
Nicholas Riley's Solution:
IKImageView and scroll bars
Now in Mountain Lion this doesn't work. After zooming in, pinch or zoom button, the image is locked in the lower left portion of the image. It won't scroll.
So the question is, what's the appropriate way to display a large image in IKImageView and have two finger dragging of the zoomed image?
Thank you,
Stateful
Well, Nicholas Riley's Solution is an ugly hack in that it addresses the wrong class; the issue isn't with NSClipView (which he subclassed, but which works just fine as is), but with IKImageView.
The issue with IKImageView is actually quite simple (God knows why Apple hasn't fixed this in what? … 7 years ...): Its size does not adjust to the size of the image it displays. Now, when you embed an IKImageView in an NSScrollView, the scroll view obviously can only adjust its scroll bars relative to the size of the embedded IKImageView, not to the image it contains. And since the size of the IKImageView always stays the same, the scroll bars won't work as expected.
The following code subclasses IKImageView and fixes this behavior. Alas, it won't fix the fact that IKImageView is crash-prone in Mountain Lion as soon as you zoom …
///////////////////// HEADER FILE - FixedIKImageView.h
#import <Quartz/Quartz.h>
#interface FixedIKImageView : IKImageView
#end
///////////////////// IMPLEMENTATION FILE - FixedIKImageView.m
#import "FixedIKImageView.h"
#implementation FixedIKImageView
- (void)awakeFromNib
{
[self setTranslatesAutoresizingMaskIntoConstraints:NO]; // compatibility with Auto Layout; without this, there could be Auto Layout error messages when we are resized (delete this line if your app does not use Auto Layout)
}
// FixedIKImageView must *only* be used embedded within an NSScrollView. This means that setFrame: should never be called explicitly from outside the scroll view. Instead, this method is overwritten here to provide the correct behavior within a scroll view. The new implementation ignores the frameRect parameter.
- (void)setFrame:(NSRect)frameRect
{
NSSize imageSize = [self imageSize];
CGFloat zoomFactor = [self zoomFactor];
NSSize clipViewSize = [[self superview] frame].size;
// The content of our scroll view (which is ourselves) should stay at least as large as the scroll clip view, so we make ourselves as large as the clip view in case our (zoomed) image is smaller. However, if our image is larger than the clip view, we make ourselves as large as the image, to make the scrollbars appear and scale appropriately.
CGFloat newWidth = (imageSize.width * zoomFactor < clipViewSize.width)? clipViewSize.width : imageSize.width * zoomFactor;
CGFloat newHeight = (imageSize.height * zoomFactor < clipViewSize.height)? clipViewSize.height : imageSize.height * zoomFactor;
[super setFrame:NSMakeRect(0, 0, newWidth - 2, newHeight - 2)]; // actually, the clip view is 1 pixel larger than the content view on each side, so we must take that into account
}
//// We forward size affecting messages to our superclass, but add [self setFrame:NSZeroRect] to update the scroll bars. We also add [self setAutoresizes:NO]. Since IKImageView, instead of using [self setAutoresizes:NO], seems to set the autoresizes instance variable to NO directly, the scrollers would not be activated again without invoking [self setAutoresizes:NO] ourselves when these methods are invoked.
- (void)setZoomFactor:(CGFloat)zoomFactor
{
[super setZoomFactor:zoomFactor];
[self setFrame:NSZeroRect];
[self setAutoresizes:NO];
}
- (void)zoomImageToRect:(NSRect)rect
{
[super zoomImageToRect:rect];
[self setFrame:NSZeroRect];
[self setAutoresizes:NO];
}
- (void)zoomIn:(id)sender
{
[super zoomIn:self];
[self setFrame:NSZeroRect];
[self setAutoresizes:NO];
}
- (void)zoomOut:(id)sender
{
[super zoomOut:self];
[self setFrame:NSZeroRect];
[self setAutoresizes:NO];
}
- (void)zoomImageToActualSize:(id)sender
{
[super zoomImageToActualSize:sender];
[self setFrame:NSZeroRect];
[self setAutoresizes:NO];
}
- (void)zoomImageToFit:(id)sender
{
[self setAutoresizes:YES]; // instead of invoking super's zoomImageToFit: method, which has problems of its own, we invoke setAutoresizes:YES, which does the same thing, but also makes sure the image stays zoomed to fit even if the scroll view is resized, which is the most intuitive behavior, anyway. Since there are no scroll bars in autoresize mode, we need not add [self setFrame:NSZeroRect].
}
- (void)setAutoresizes:(BOOL)autoresizes // As long as we autoresize, make sure that no scrollers flicker up occasionally during live update.
{
[self setHasHorizontalScroller:!autoresizes];
[self setHasVerticalScroller:!autoresizes];
[super setAutoresizes:autoresizes];
}
#end

Core Animation with an NSView and subviews

I've subclassed NSView to create a 'container' view (which I've called TRTransitionView) which is being used to house two subviews. At the click of a button, I want to transition one subview out of the parent view and transition the other in, using the Core Animation transition type: kCATransitionPush. For the most part, I have this working as you'd expect (here's a basic test project I threw together).
The issue I'm seeing relates to resizing my window and then toggling between my two views. After resizing a window, my subviews will appear at seemingly random locations within my TRTransitionView. Additionally, it appears as if the TRTransitionView hasn't stretched correctly and is clipping the contents of its subviews. Ideally, I would like subviews anchored to the top-left of their parent view at all times, and to also grow to expand the size of the parent view.
The second issue relates to an NSTableView I've placed in my first subview. When my window is resized, and my TRTransitionView resizes to match its new dimensions, my TableView seems to resize its content quite awkwardly (the entire table seems to jolt around) and the newly expanded space that the table now occupies seems to 'flash' (as if in the process of being animated). Extremely difficult to describe, but is there any way to stop this?
Here's my TRTransitionView class:
-(void) awakeFromNib
{
[self setWantsLayer:YES];
[self addSubview:[self currentView]];
transition = [CATransition animation];
[transition setType:kCATransitionPush];
[transition setSubtype:kCATransitionFromLeft];
[self setAnimations: [NSDictionary dictionaryWithObject:transition forKey:#"subviews"]];
}
- (void)setCurrentView:(NSView*)newView
{
if (!currentView) {
currentView = newView;
return;
}
[[self animator] replaceSubview:currentView with:newView];
currentView = newView;
}
-(IBAction) switchToViewOne:(id)sender
{
[transition setSubtype:kCATransitionFromLeft];
[self setCurrentView:viewOne];
}
-(IBAction) switchToViewTwo:(id)sender
{
[transition setSubtype:kCATransitionFromRight];
[self setCurrentView:viewTwo];
}

Making NSScroller larger

I'm making a Cocoa application optimized for external touch sensitive screens, so I want to make a large scollbar in an NSScrollView. I've tried just to resize the scrollers frame, but both drawing and mouse events only happens in the scroll area, it doesn't get wider (it's the vertical scroller). This is the code I'm using (from the subclassed NSScrollView):
- (void)tile{
[super tile];
NSScroller *vertScroll = [self verticalScroller];
NSRect vertScrollFrame = [vertScroll frame];
NSView *content = [self contentView];
NSRect contentFrame = [content frame];
contentFrame.size.width -= 50;
vertScrollFrame.origin.x -= 50;
vertScrollFrame.size.width += 50;
[vertScroll setFrame:vertScrollFrame];
[content setFrame:contentFrame];
}
I've tried to subclass the NSScroller, but I don't know how I would extent it.
Does your customized scroll view exist in a nib/xib? If so, have you set the scroll view's class identity in Interface Builder to the name of your custom class? It's found in the "Identity" tab of the Inspector window.

Resources