TTImageView refresh image - three20

I have the following code
TTImageView* chart = [[[TTImageView alloc] initWithFrame:CGRectMake(2,2,30, 30)] autorelease];
chart.backgroundColor = [UIColor clearColor];
chart.defaultImage = nil;
chart.urlPath = #"http://test.com/test.png";
And I need to refresh the image, so I call reload function on the
chart. However the chart is not refreshed.
I found out that TTURLCache cached the image. So in app delegate when
app just started i do the following:
[TTURLCache sharedCache].disableImageCache = YES;
[[TTURLCache sharedCache] removeAll:YES];
However, the image is still not refreshed. Any help would be
appreciated. I also realized that whenever I do [chart reload], and check [chart isLoading] it is always true, which means that the request is not sent somehow.

Add a reload call after you set the URL and make sure you are calling from the main thread
[chart reload];

I ran into a similar issue when I tried loading my users avatars into a UIButton view. I needed to use TTImageView's delegate protocol to update the button's image. For example:
self.avatarImageView = [[TTImageView alloc] initWithFrame:CGRectMake(10, 10, 50, 50)];
self.avatarImageView.defaultImage = TTIMAGE(#"bundle://defaultAvatar.png");
self.avatarImageView.delegate = self;
self.avatarImageView.urlPath = #"http://www.site.com/path/to/image.png"
Once the urlPath is set, the request is made in the background. When you conform to the TTImageViewDelegate, you can access the following delegate methods.
/**
* Called when the image begins loading asynchronously.
*/
- (void)imageViewDidStartLoad:(TTImageView*)imageView {
NSLog(#"loading image...");
}
/**
* Called when the image finishes loading asynchronously.
*/
- (void)imageView:(TTImageView*)imageView didLoadImage:(UIImage*)image {
NSLog(#"loaded image!");
[self.avatarImageButton setImage:image forState:UIControlStateNormal];
}
/**
* Called when the image failed to load asynchronously.
* If error is nil then the request was cancelled.
*/
- (void)imageView:(TTImageView*)imageView didFailLoadWithError:(NSError*)error {
NSLog(#"error loading image - %#", error);
}
When the image has loaded successfully, I can use it for my UIButton as you'll see in the imageView:didLoadImage method.
Hope this helps shed some light on this issue. I know it's an old question, but maybe it'll help others later on.

After clearing the cache you should also reset the TTImageView. A simple way to do it would be to call [chart unsetImage] and then set the urlPath again.
Another way to do it without clearing the cache would be to add a dummy parameter to the url.
chart.urlPath = #"http://test.com/test.png?dummy=dummy";

Related

Modal View is Resetting My Existing View Controller

I load a view controller in my code using a segue in UIStoryboard. Based on a few user selected options, I then hide or show a UI control using an animation. It all works fine.
- (void)showComments {
NSLog(#"Show Comments");
_commentsTextView.hidden = NO;
_commentsTextView.frame = CGRectMake(435, 266, 475, 134);
[UIView animateWithDuration:1 animations:^{
_commentsTextView.alpha = 1;
_signatureButton.frame = CGRectMake(435, 420, 475, 134);
_cancelButton.frame = CGRectMake(568, 581, 100, 44);
_finishedButton.frame = CGRectMake(676, 581, 100, 44);
}];
}
Then I am presenting a view controller created in UIStoryboard using the following code:
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"MainStoryboard_iPad" bundle:nil];
SigningViewController *signingVC = (SigningViewController *)[storyboard instantiateViewControllerWithIdentifier:#"SigningViewController"];
signingVC.object = self;
signingVC.signatureProperty = #"employeeSignature";
signingVC.startDate = self.startDate;
signingVC.endDate = self.endDate;
[self presentViewController:signingVC animated:YES completion:nil];
When this code fires all happens as expected except for one thing: All the custom animations that hid or showed a UI control revert back. It is as if by calling the presentViewController method, it is redrawing my existing view from the UIStoryboard.
Is there a way to get it to quit redrawing/reload my existing view from the Storyboard before displaying the new view modally?
I had the same problem too. Whatever I tried to make an animation change whenever a modal closed it seemed to reset back to the original storyboard.
The solution I had to go with was to add any animation view as an outlet to the header file. Take off the view out of the self.view or main view of the view controller on the storyboard and programmatically add it as a subview. Set the frame to where you want it and all the animation positions should be the same after you end the modal. It worked for me if you need more explanation and let me know.
I had just the same problem and posted it here in a more detailed form, A user named kokx helped me a lot and the answer is documented in my question page.

Correct way of updating an NSTableView in a Core Data app

I have a Core Data project with an NSTableView where the columns are bound to an NSArrayController. In turn, the controller's content is bound to the main managed object context of the AppDelegate.
I've sub-classed NSTextFieldCell's to provide a more customised way of displaying the data. Everything seems to work well but when I enumerate through the objects in the context and change an attribute, the NSArrayController doesn't seem to pass the new values through to the table. I know they're being changed as the sortDescriptors work and the table organises itself according to the new data but while still displaying the old values. I'm guessing this is down to the recycling of the NSCell's that display the data: although the underlying values have been changed in the Core Data DB they're not re-drawing.
After trying various KVO notifications with [myArrayController willChangeValueFor: aKeyPath] and the corresponding didChange as well as simply [myTable reloadData]. My only solution has been to unhook the managedObjectContext binding of the controller, and re-hook it after processing, like this:
self.hasBackgroundOp = YES;
self.isCancelled = NO; //can get changed with a 'cancel' button in the UI
if (self.managedObjectContext.hasChanges) {
[self. managedObjectContext save:nil];
}
[myArrayController setManagedObjectContext:nil]; //brutal un-hooking of controller
dispatch_group_t dispatchGroup = dispatch_group_create();
dispatch_group_async(dispatchGroup, dispatch_get_global_queue(0, 0), ^{
.
.
// create a thread context, set the persistent store co-ord
// and do some processing on the
// NSManagedObjects that are fetched into an array just
// for the purpose of this thread
.
.
[ctx save:&error];
//and after the processing is finished then refresh the table
dispatch_async(dispatch_get_main_queue(), ^{
[myArrayController setManagedObjectContext:self.managedObjectContext]; //this forces re-drawing of the table
self.hasBackgroundOp = NO;
self.isCancelled = NO;
});
});
This seems really brutal to be doing [myArrayController setManagedObjectContext:nil]; and then setting it again just to refresh the table's contents. I just can't believe I've done this correctly although it works just fine. Could anyone advise of a better way? Thanks!
UPDATE: actually setting the managed object context doesn't solve the problem. It seems to have stopped working. If the code is run twice in a row then the app just hangs. Comment out the -setManagedObjectContext: lines and the code can be run as many times as required.
In response to the comments, my custom cell uses the following to display itself:
NSInteger index = [[self stringValue] integerValue] + 1;
NSAttributedString *cellText = [[NSAttributedString alloc] initWithString:[NSString stringWithFormat:#"%lu",index] attributes:attributes];
NSRect textRect;
textRect.origin = cellFrame.origin;
textRect.origin.x += (cellFrame.size.width - cellText.size.width) / 2;
textRect.origin.y += (cellFrame.size.height - cellText.size.height) / 2;
textRect.size.width = cellText.size.width;
textRect.size.height = cellText.size.height;
[cellText drawInRect:textRect];
The array controller is setup in the main nib and has it's managed object context bound to the AppDelegate's. There are no fetch requests or predicates in the nib - a sort descriptor is bound in the AppDelegate's code:
[myArrayController setSortDescriptors:[NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:kINDEX ascending:YES]]];
Think I've found it - I added the following to the end of the threaded processing and executed it on the main thread:
[self.managedObjectContext reset];
[myArrayController fetch:self];
Resetting the MOC apparently clears it so forcing the controller to redraw the cells. The -fetch appears to be necessary otherwise the table goes blank...

UIPopoverViewController Adding Subviews is slow

I have a UIPopoverViewController that is displaying a custom UIViewController properly. When I click a button I have an action run and as a result I add a view to the view hierarchy of the UIViewController's view.
The problem is that it is very slow, and takes several seconds for the view to appear. I'm not doing anything out of the ordinary with my UIViewController's code.
- (void)showAccountChooser {
self.twitterAccountPicker = [TwitterAccountPicker new];
[self.view addSubview:self.twitterAccountPicker.view];
self.twitterAccountPicker.view.frame = self.view.bounds;
self.twitterAccountPicker.view.transform = CGAffineTransformMakeScale(.05, .05);
[UIView animateWithDuration:0.5f animations:^{
self.twitterAccountPicker.view.transform = CGAffineTransformMakeScale(1, 1);
} completion:^(BOOL finished) {
//[self.twitterAccountPicker viewDidAppear:YES];
}];
}
The UIViewController that I'm adding is trivial and does not heavy processing in the viewDidLoad or viewWill/DidAppear. I have set break points and verified that it is not doing anything bad.
Anyone else notice this when adding views?
After setting break points trying to debug this, I realized that my showAccountChooser method was being called from a block invoke, which was happening on a background thread. Moving this call to the main thread resolved the issue.

Trying to cache uiImages in dictionary, doesn't seem to affect loading time

I'm using a standard caching method to cache some UIImages loaded from the Documents directory. The images are being displayed in a UITableView. They're pretty large – the images themselves are up to 600x600 and are displayed in imageviews that are 240x180 (on a retina display, so the res discrepancy is not large).
Loading the images in realtime causes some lag when a new cell is about to come onscreen. So I've implemented a caching method in the object that handles the image:
- (UIImage *)imageWithStyle:(NSString *)styleName {
NSLog(#"looking for image %#", self.imageFileName);
/* if we have a cached image in dictionary, return it */
if (imageCache == nil) imageCache = [[NSMutableDictionary alloc] init];
UIImage *returnImage = [imageCache objectForKey:styleName];
if (returnImage != nil) {
NSLog(#"returning cached image");
return returnImage;
}
/* otherwise, look for image at path */
NSString *path = [self cacheFilePathWithStyle:styleName];
UIImage * originalImage = [[UIImage alloc] initWithContentsOfFile:path];
/* if image doesnt exist at path, start download and return nil */
if (originalImage == nil) {
NSLog(#"image not found. downloading.");
[self downloadImageFromS3];
return nil;
}
/* found image at path */
/* scale image for screen */
if ([[UIScreen mainScreen] respondsToSelector:#selector(scale)] && [[UIScreen mainScreen] scale] == 2){
returnImage = [UIImage imageWithCGImage:[[originalImage autorelease] CGImage] scale:2.0 orientation:UIImageOrientationUp];
NSLog(#"scaling image for retina");
} else {
returnImage = [originalImage autorelease];
NSLog(#"image scaled for standard resolution");
}
/* cache image in dictionary */
NSLog(#"caching image");
[imageCache setObject:returnImage forKey:styleName];
return returnImage;
}
Before the tableview appears on screen, I force all of the image handling objects to run this caching method to ensure that the images are present in the dictionary to be retrieved when they need to be displayed. By the NSLog's I can see that things are working as they should.
I'm getting lag-free performance now, but only after the image has appeared once on screen. So, when I initially see the tableview, I scroll down and the NSLogs tell me that the images are being retrieved from the cache, but still I get the same loading lag. After a cell has appeared on screen once, it thereafter loads up with no lag.
Is there something I'm missing here? Is there something more that I have to to to actually cache the image? Loading it and putting it in the dictionary doesn't seem to do the trick.
Thanks!
UPDATE
I've given up on this for now. There are some attempts out there to force the images to load by drawing them in a new context, but I'm not familiar with core graphics programming at this point. I've tried some code that folks have shared, but with no luck.
Instead, I'm going to display low-res versions of the images while the tableview is scrolling and load high-res versions when the tableview stops scrolling as announced through its delegate methods. At least I know this approach will work with any number of images.
From the documentation for -[UIImage initWithContentsOfFile:]:
This method loads the image data into memory and marks it as purgeable. If the data is purged and needs to be reloaded, the image object loads that data again from the specified path.
My guess is that, by loading all images into memory, your app consumes too much memory, causing the UIImage class to release the image data it can reload from files later.
If you use [UIImage imageNamed:], it'll do all this caching business for you. No need to roll your own cache and then wonder if it's working.
An upside and a downside to using that method. Upside: If you use the same image file twice, it won't actually load it twice, saving you that memory. Downside: Caching has a big memory impact, and you want to think pretty hard about doing it, whether you roll your own or not.

Cocoa QuickLook initiated by NSTableView Cell

I have an NSTableView that contains 2 different Columns - one is an NSImageCell that shows a file icon, and the second is a custom subclass of NSTextFieldCell that contains a quick look button on the right of the text. When I click the Quick Look button, the following code is invoked:
[[QLPreviewPanel sharedPreviewPanel] makeKeyAndOrderFront:nil];
This does it's job and shows the blank Quick Look panel saying "No Items Selected." After I did a bit of research on the internet, I implemented a custom NSTableView subclass to be the Delegate and Data Source for the Quick Look panel. I get the notification that Quick Look asks if I want to be the delegate, and I respond with return YES. Even though I implement all methods in both QLPreviewPanelDataSource and QLPreviewPanelDelegate, at runtime I get this error on the console:
2010-12-24 15:32:17.235 BackMeUp[4763:80f] clicked: ~/Desktop/HUDTape.mov
2010-12-24 15:32:17.489 BackMeUp[4763:80f] [QL] QLError(): -[QLPreviewPanel setDelegate:] called while the panel has no controller - Fix this or this will raise soon.
See comments in QLPreviewPanel.h for -acceptsPreviewPanelControl:/-beginPreviewPanelControl:/-endPreviewPanelControl:.
2010-12-24 15:32:17.490 BackMeUp[4763:80f] [QL] QLError(): -[QLPreviewPanel setDataSource:] called while the panel has no controller - Fix this or this will raise soon.
See comments in QLPreviewPanel.h for -acceptsPreviewPanelControl:/-beginPreviewPanelControl:/-endPreviewPanelControl:.
2010-12-24 15:32:17.491 BackMeUp[4763:80f] We can now receive QL Events.
2010-12-24 15:32:18.291 BackMeUp[4763:80f] -[NSPathStore2 stringValue]: unrecognized selector sent to instance 0x5ecb10
2010-12-24 15:32:18.292 BackMeUp[4763:80f] -[NSPathStore2 stringValue]: unrecognized selector sent to instance 0x5ecb10
And the Quick Look panel does not show up, which I find rather odd. The first line above is just that I know the cell has been clicked. Anyways, here is the .m file of the custom table view subclass:
//
// BackupListTableView.m
// BackMeUp
//
// Created by Tristan Seifert on 12/24/10.
// Copyright 2010 24/7 Server. All rights reserved.
//
#import "BackupListTableView.h"
#implementation BackupListTableView
- (void) awakeFromNib {
}
// Quick Look Delegates
- (BOOL)acceptsPreviewPanelControl:(QLPreviewPanel *)panel;
{
[QLPreviewPanel sharedPreviewPanel].delegate = self;
[QLPreviewPanel sharedPreviewPanel].dataSource = self;
NSLog(#"We can now receive QL Events.");
return YES;
}
- (void)beginPreviewPanelControl:(QLPreviewPanel *)panel
{
// This document is now responsible of the preview panel
// It is allowed to set the delegate, data source and refresh panel.
[QLPreviewPanel sharedPreviewPanel].delegate = self;
[QLPreviewPanel sharedPreviewPanel].dataSource = self;
}
- (void)endPreviewPanelControl:(QLPreviewPanel *)panel
{
// This document loses its responsisibility on the preview panel
// Until the next call to -beginPreviewPanelControl: it must not
// change the panel's delegate, data source or refresh it.
return;
}
// Quick Look panel data source
- (NSInteger)numberOfPreviewItemsInPreviewPanel:(QLPreviewPanel *)panel
{
return 1;
}
- (id <QLPreviewItem>)previewPanel:(QLPreviewPanel *)panel previewItemAtIndex:(NSInteger)index
{
int selectedRow = [self selectedRow];
return [NSURL URLWithString:[[[self dataSource] tableView:self objectValueForTableColumn:fileColumn row:selectedRow] stringValue]];
}
// Quick Look panel delegate
- (BOOL)previewPanel:(QLPreviewPanel *)panel handleEvent:(NSEvent *)event
{
// redirect all key down events to the table view
return NO;
}
// This delegate method provides the rect on screen from which the panel will zoom.
- (NSRect)previewPanel:(QLPreviewPanel *)panel sourceFrameOnScreenForPreviewItem:(id <QLPreviewItem>)item
{
NSRect iconRect = [self rectOfColumn:1];
/*
// check that the icon rect is visible on screen
NSRect visibleRect = [self visibleRect];
// convert icon rect to screen coordinates
iconRect = [self convertRectToBase:iconRect];
iconRect.origin = [[self window] convertBaseToScreen:iconRect.origin];
*/
return iconRect;
}
// This delegate method provides a transition image between the table view and the preview panel
- (id)previewPanel:(QLPreviewPanel *)panel transitionImageForPreviewItem:(id <QLPreviewItem>)item contentRect:(NSRect *)contentRect
{
int selectedRow = [self selectedRow];
NSImage *fileIcon = [[NSWorkspace sharedWorkspace] iconForFile:[[[self dataSource] tableView:self objectValueForTableColumn:fileColumn row:selectedRow] stringValue]];
return fileIcon;
}
#end
Thanks for any help.
The documentation isn't the best for this, since it's a new feature that was added in 10.6. (Well, there is obviously the class and protocol references, but in my experience, I've always found the Companion Guides to be more helpful in understanding how the objects are intended to be used in a real-world scenario).
The QLPreviewPanelController Protocol Reference defines 3 methods:
QLPreviewPanelController Protocol Reference
The Quick Look preview panel shows previews for items provided by the first object in the responder chain that implements the methods in this protocol. You typically implement these methods in your window controller or delegate. You should never try to modify preview panel state if you’re not controlling the panel.
- (BOOL)acceptsPreviewPanelControl:(QLPreviewPanel *)panel;
- (BOOL)beginPreviewPanelControl:(QLPreviewPanel *)panel;
- (void)endPreviewPanelControl:(QLPreviewPanel *)panel;
I'm guessing that your code should look like this:
- (BOOL)acceptsPreviewPanelControl:(QLPreviewPanel *)panel
{
return YES;
}
You shouldn't be doing anything in that method besides returning YES. acceptsPreviewPanelControl: is sent to every object in the responder chain until something returns YES. By returning YES, that object effectively becomes "the controller". The latter 2 methods are called on the controller object after it returns YES from the first method. So you should only be setting the delegate and datasource in the beginPreviewPanelControl: method (at which time you will be regarded as the current controller).
- (void)beginPreviewPanelControl:(QLPreviewPanel *)panel
{
// This document is now responsible of the preview panel
// It is allowed to set the delegate, data source and refresh panel.
[QLPreviewPanel sharedPreviewPanel].delegate = self;
[QLPreviewPanel sharedPreviewPanel].dataSource = self;
NSLog(#"We can now receive QL Events.");
}
First:
-acceptsPreviewPanelControl should only return YES and not try to set delegate and datasource.
Then, the problem is that you get an exception breaking the panel:
2010-12-24 15:32:18.291 BackMeUp[4763:80f] -[NSPathStore2 stringValue]: unrecognized selector sent to instance 0x5ecb10
The exception is very likely caused by these invocations:
[[[self dataSource] tableView:self objectValueForTableColumn:fileColumn row:selectedRow] stringValue]
Very likely, [[self dataSource] tableView:self objectValueForTableColumn:fileColumn row:selectedRow] is a file path (a NSPathStore instance which is a subclass of NSString) so it does not respond to -stringValue
So replace:
[NSURL URLWithString:[[[self dataSource] tableView:self objectValueForTableColumn:fileColumn row:selectedRow] stringValue]]
by:
[NSURL fileURLWithPath:[[self dataSource] tableView:self objectValueForTableColumn:fileColumn row:selectedRow]]
Also remove the call to -stringValue in transitionImageForPreviewItem.
As a side note, it seems to be suboptimal to load the image at each call of transitionImageForPreviewItem. I suppose you already have the image computed elsewhere (displayed in your table view), try to use it.

Resources