iPhone/Objective-C TTLauncherView loading high resolution images from a URL - iphone

I am using Three20's TTLauncherView and was wondering whether anyone has had experience loading high resolution images?
http://three20.info/showcase/launcher
I am using the following method to set my TTLauncherItem's:
NSString *imageUrl = [self displayImageUrl:#"http://foo.com/lowres.png" withHighResUrl:#"http://foo.com/hires.png";
TTLauncherItem *launcherItem = [[[TTLauncherItem alloc] initWithTitle:#"Icon1"
image:imageUrl
URL:#"Icon1"
canDelete:NO] autorelease];
This is the method I use to determine whether it's an iOS4.
- (NSString *)displayImageUrl:(NSString *)standardResUrl withHighResUrl:(NSString *)highResUrl {
NSString *imageUrl = nil;
if ([[UIScreen mainScreen] respondsToSelector:#selector(scale)] && [[UIScreen mainScreen] scale] == 2) {
imageUrl = highResUrl;
} else {
imageUrl = standardResUrl;
}
return imageUrl;
}
The problem is that images are actually getting displayed in their full dimensions on an iPhone 4, whereas any iOS device below an iPhone 4 are getting displayed properly. Just wondering whether I would need to make changes to the TTLauncherView library or whether there's an easier way to resolve such an issue.

I accomplished this by adding a new style to my three20 stylesheet based on launcherButtonImage. This is the original...
- (TTStyle*)launcherButtonImage:(UIControlState)state {
TTStyle* style =
[TTBoxStyle styleWithMargin:UIEdgeInsetsMake(-7, 0, 11, 0) next:
[TTShapeStyle styleWithShape:[TTRoundedRectangleShape shapeWithRadius:8] next:
[TTImageStyle styleWithImageURL:nil defaultImage:nil contentMode:UIViewContentModeCenter
size:CGSizeZero next:nil]]];
if (state == UIControlStateHighlighted || state == UIControlStateSelected) {
[style addStyle:
[TTBlendStyle styleWithBlend:kCGBlendModeSourceAtop next:
[TTSolidFillStyle styleWithColor:RGBACOLOR(0,0,0,0.5) next:nil]]];
}
return style;
}
...and this is the updated version...
- (TTStyle*)favoriteLauncherButtonImage:(UIControlState)state {
TTStyle* style =
[TTShapeStyle styleWithShape:[TTRoundedRectangleShape
shapeWithRadius:4.0] next:
[TTBoxStyle styleWithMargin:UIEdgeInsetsMake(0, 0, 0, 0)
padding:UIEdgeInsetsMake(16, 16, 16, 16)
minSize:CGSizeMake(0, 0)
position:TTPositionStatic next:
[TTImageStyle styleWithImageURL:nil defaultImage:nil contentMode:UIViewContentModeScaleAspectFit
size:CGSizeMake(64, 64) next: nil
]]];
if (state == UIControlStateHighlighted || state == UIControlStateSelected) {
[style addStyle:
[TTBlendStyle styleWithBlend:kCGBlendModeSourceAtop next:
[TTSolidFillStyle styleWithColor:RGBACOLOR(0,0,0,0.5) next:nil]]];
}
return style;
}
There's probably stuff in there you don't need like rounded image corners. The operative part is the TTImageStyle directive which locks the image size to 64x64. Hope this helps.

I am using Three20's TTLauncherView
Instead, try using SDWebImage:
https://github.com/rs/SDWebImage
You could just issue two loads on a UIImageView, one for the high and one for the low res image. The low-res should finish first...

Related

iPhone5 Button Placement using IF statement?

I have a game whereby the button for pause is supposed to be on the top right hand corner, however on iPhone 5 it is 3/4 way up the top, so I tried to manually set it but I cannot get it to work, this works, however on the iPhone 3 & 4 it does not show (so it's obviously ignoring some part of the IF statement)
CCMenuItemSprite* item1 = [CCMenuItemSprite itemWithNormalSprite:[CCSprite spriteWithFile:[[MainInfo shareInstance] getImageName:#"pause1.png"]]
selectedSprite:[CCSprite spriteWithFile:[[MainInfo shareInstance] getImageName:#"pause2.png"]]
target:self
selector:#selector(soundOff)];
CCMenu *menu = [CCMenu menuWithItems:item1, nil];
if ( ![[MainInfo shareInstance] isIPad] )
menu.position = ccp(290, 450); //450 iPhone3/4
// else
if (IS_IPHONE_5) {
// do something specific for iPhone 5
menu.position = ccp(290, 530); //450 iPhone3/4
}else
menu.position = ccp(696, 960);
[_bottomFrame addChild:menu z:30 tag:1234];
[self updateBoard];
}
At the top of the .m file I have the following;
#define IS_IPHONE_5 ([UIScreen mainScreen].bounds.size.height == 568.0)
Could anyone please help with a correct way to write the code above?
Many thanks
Chris
Your ELSE statment is not enclosed in brackets furthoremore:
CGSize screenSize = [[UIScreen mainScreen] bounds].size;
if (screenSize.height > 480.0f) {
/*Do iPhone 5 stuff here.*/
} else {
/*Do iPhone Classic stuff here.*/
}

New image name for iPhone 5

With the retina we make images with the #2x in the name. I see where the default image has to be default-568h#2x but this does not seem to be the case for other images. Like if my background is bg.png and bg#2x.png I tried using bg-568h#2x.png but that does not work. Can somebody tell me what the images need to be named to support the iPhone 5?
No special suffix for iPhone 5 (4'' display), just the specific Default-568h#2x.png file.
Here's a macro to handle it:
// iPhone 5 support
#define ASSET_BY_SCREEN_HEIGHT(regular, longScreen) (([[UIScreen mainScreen] bounds].size.height <= 480.0) ? regular : longScreen)
Usage: (assets names - image.png, image#2x.png, image-568h#2x.png)
myImage = [UIImage imageNamed:ASSET_BY_SCREEN_HEIGHT(#"image",#"image-568h")];
There is no specific image name. Having the Default-568h#2x will launch that image on an iPhone 5 or iPod Touch 5G and will enable the non-letterbox mode. After that, you need to design your views to be flexible. There is no special "image name" or anything for the new size.
For your background, for example, you should probably be using an image that is capable of stretching or tiling and have it configured properly before setting it.
iPhone 5 does not have a different pixel density, it's the same retina display PPI as the iPhone 4/4S, it's just a different screen size. The #2x images will be used on iPhone 5 as well as 4/4S.
To complete Jason's answser, I would propose: What about overriding the UIImage's imageNamed: method to have it happen the "-568" suffix to the name of your image? Or add a new method called resolutionAdaptedImageNamed: to the UIImage maybe using a category.
If I have a bit of time in the next days, I will try to post the code for that.
Caution: will not work for images in the Nib files.
If you are using Xcode 5, you can use asset catalog (see usage there Apple's documentation)
Once your asset catalog is created [ UIImage imagedNamed: #"your_image_set" ] will pull right image based on device.
You can also make category for this just make category as below .
UIImage+Retina4.h
#import <UIKit/UIKit.h>
#import <objc/runtime.h>
#interface UIImage (Retina4)
#end
UIImage+Retina4.m
#import "UIImage+Retina4.h"
static Method origImageNamedMethod = nil;
#implementation UIImage (Retina4)
+ (void)initialize {
origImageNamedMethod = class_getClassMethod(self, #selector(imageNamed:));
method_exchangeImplementations(origImageNamedMethod,
class_getClassMethod(self, #selector(retina4ImageNamed:)));
}
+ (UIImage *)retina4ImageNamed:(NSString *)imageName {
// NSLog(#"Loading image named => %#", imageName);
NSMutableString *imageNameMutable = [imageName mutableCopy];
NSRange retinaAtSymbol = [imageName rangeOfString:#"#"];
if (retinaAtSymbol.location != NSNotFound) {
[imageNameMutable insertString:#"-568h" atIndex:retinaAtSymbol.location];
} else {
CGFloat screenHeight = [UIScreen mainScreen].bounds.size.height;
if ([UIScreen mainScreen].scale == 2.f && screenHeight == 568.0f) {
NSRange dot = [imageName rangeOfString:#"."];
if (dot.location != NSNotFound) {
[imageNameMutable insertString:#"-568h#2x" atIndex:dot.location];
} else {
[imageNameMutable appendString:#"-568h#2x"];
}
}
}
NSString *imagePath = [[NSBundle mainBundle] pathForResource:imageNameMutable ofType:#"png"];
if (imagePath) {
return [UIImage retina4ImageNamed:imageNameMutable];
} else {
return [UIImage retina4ImageNamed:imageName];
}
return nil;
}
#end
And you can directly check using import this category as below where you wont to check 568 or normal image
imgvBackground.image=[UIImage imageNamed:#"bkground_bg"];//image name without extantion

Preload an additional image using Apple's PhotoScroller example

I am trying to modify Apple's PhotoScroller example and I encountered a problem that I couldn't solve.
Basically, the PhotoScroller originally loaded a bunch of image in an array locally. I try to modify this and change to it request an image file dynamically from an URL. Once the user scroll to the next page, it will fetch the next image from a new URL.
In order to improve the performance, I wanted to preload the next page so user doesn't need to wait for the image being downloaded while scrolling to the next page. Once the next page is on current page, the page after that will be loaded and so on...
I'm not quite sure how I can achieve this and I hope someone can show me what to do.
Here is my custom code: (Please refer the full code from Apple's PhotoScroller example)
tilePage method: (It will be call at the beginning and every time when user did scroll the scrollView)
- (void)tilePages
{
// Calculate which pages are visible
CGRect visibleBounds = pagingScrollView.bounds;
int firstNeededPageIndex = floorf(CGRectGetMinX(visibleBounds) / CGRectGetWidth(visibleBounds));
int lastNeededPageIndex = floorf((CGRectGetMaxX(visibleBounds)-1) / CGRectGetWidth(visibleBounds));
firstNeededPageIndex = MAX(firstNeededPageIndex, 0);
lastNeededPageIndex = MIN(lastNeededPageIndex, [self imageCount] - 1);
// Recycle no-longer-visible pages
for (ImageScrollView *page in visiblePages) {
if (page.index < firstNeededPageIndex || page.index > lastNeededPageIndex) {
[recycledPages addObject:page];
[page removeFromSuperview];
}
}
[visiblePages minusSet:recycledPages];
// add missing pages
for (int index = firstNeededPageIndex; index <= lastNeededPageIndex; index++) {
if (![self isDisplayingPageForIndex:index]) {
ImageScrollView *page = [self dequeueRecycledPage];
//ImageScrollView *nextpage = [self dequeueRecycledPage];
if (page == nil) {
page = [[[ImageScrollView alloc] init] autorelease];
}
[self configurePage:page forIndex:index];
[pagingScrollView addSubview:page];
[visiblePages addObject:page];
}
}
}
To configure page index and content:
- (void)configurePage:(ImageScrollView *)page forIndex:(NSUInteger)index
{
//set page index
page.index = index;
//set page frame
page.frame = [self frameForPageAtIndex:index];
//Actual method to call image to display
[page displayImage:[self imageAtIndex:index]];
NSLog(#"index: %i", index);
}
To fetch image from URL:
- (UIImage *)imageAtIndex:(NSUInteger)index {
NSString *string1 = [NSString stringWithString:#"http://abc.com/00"];
NSString *string2 = [NSString stringWithFormat:#"%d",index+1];
NSString *string3 = [NSString stringWithString:#".jpg"];
NSString *finalString = [NSString stringWithFormat:#"%#%#%#",string1,string2,string3];
NSLog(#"final string is: %#", finalString);
NSURL *imgURL = [NSURL URLWithString: finalString];
NSData *imgData = [NSData dataWithContentsOfURL:imgURL];
return [UIImage imageWithData:imgData];
}
Thanks for helping!
Lawrence
You can use concurrent operations to fetch images one after another. Using NSOperations, you can set a dependency chain so images are loaded in a serial fashion in the background, or you can have them all downloaded concurrently.
The problem with large images is that even though you save them in the file system, there is a "startup" time to get the images rendered. Also, in PhotoScroller, Apple "cheats" by having all the images pre tiled for each level of detail. Apple provides no way to render just a Plain ole JPEG in PhotoScroller, so unless you can pre tile it will be of no use to you.
If you are curious, you can see how to both download multiple images and pre tile them into a temporary file by perusing the PhotoScrollerNetwork project on github.

Not able to get the UIImage from MPMediaItemPropertyArtWork

I have the following code to get the UIImage from MPMediaItemPropertyArtWork to display the coverArt in my view. I get a non-NULL artwork from the mediaItem, however, when I try to extract the UIImage from the artwork, it returns NULL and I don't see anything in my UIImageView. Any help will be greatly appreciated.
CGSize artworkSize = CGSizeMake(30, 30);
UIImage *artworkImage;
MPMediaItemArtwork *artwork = [song valueForProperty: MPMediaItemPropertyArtwork];
if (artwork) {
NSLog(#"artwork available");
artworkImage = [artwork imageWithSize:artworkSize];
} else {
NSLog(#"artwork not available");
artworkImage = [UIImage imageNamed:#"EmptyAlbum.png"];
}
NSLog(#"artworkImage = %#", artworkImage);
coverArtView.image = artworkImage;
Try with this - have use a UItableview to display album details.
declare MPMediaItemCollection *userMediaCollection;
-(void)mediaPicker:(MPMediaPickerController *)mediaPicker didPickMediaItems:(MPMediaItemCollection *)mediaItemCollection {
[self setUserMediaCollection:mediaItemCollection];
[self dismissModalViewControllerAnimated:YES];
}
MPMediaItem *mItem = (MPMediaItem *)[userMediaCollection.items objectAtIndex:indexPath.row];
MPMediaItemArtwork *artWork = [mItem valueForProperty:MPMediaItemPropertyArtwork];
cell.imageView.image = [artWork imageWithSize:CGSizeMake(30, 30)];
where userMediaCollection is your MPMediaItemCollection
try this... it is work.
CGSize artworkSize = CGSizeMake(30, 30);
UIImage *artworkImage;
MPMediaItemArtwork *artwork = [song valueForProperty: MPMediaItemPropertyArtwork];
artworkImage = [artwork imageWithSize:artworkSize];
if (artworkImage == nil) {
NSLog(#"artwork not available");
artworkImage = [UIImage imageNamed:#"EmptyAlbum.png"];
}
coverArtView.image = artworkImage;
I know this question is a bit old by now, but for anyone finding this, this might help:
I was scratching my head over this for a few hours, because getting the artwork was working in one view controller in my app but not in my UITableViewController. My code looked exactly the same in both instances (and very similar to yours) except for ONE thing:
let albumArtImage = albumArt.imageWithSize(cell.imageView.bounds.size)
the SIZE! (Sorry for writing this in Swift, but you get the idea). It sounds stupid, but the MPMediaItemArtwork refused to give me a small image. I literally just changed it to a bigger size, like so:
let albumArtImage = albumArt.imageWithSize(CGSize(width: 150, height: 150))
and it worked. You can resize the UIImage to fit your needs later anyway.
If anyone finds this and this (stupid) solution works, please let me know via a reply or something.
How do you initialize you MPMusicPlayerController? It should be:
MPMusicPlayerController *player = [MPMusicPlayerController iPodMusicPlayer];
See answer: https://stackoverflow.com/a/26463261/883413
Appears to be an iOS bug. If the image returned is nil, try using the artwork size instead.
UIImage *image = [artwork imageWithSize:size];
if (image == nil) {
image = [artwork imageWithSize:artwork.bounds.size];
}

Remove UIWebView Shadow?

Does anyone know if its possible to remove the shadow that is placed on the UIWebView window?
Example: http://uploadingit.com/files/1173105_olub5/shadow.png
If its possible how do you do it?
Thanks
This is a cleaner alternative to "Nikolai Krill" solution. This only hides UIImageViews within the UIWebView and not the UIWebBrowserView.
for (UIView *view in [[[webView subviews] objectAtIndex:0] subviews]) {
if ([view isKindOfClass:[UIImageView class]]) view.hidden = YES;
}
Thanks
James
the small for loop is very dangerous because it can crash if apple changes the number of the subviews.
this way it does at least not crash when something changes:
if ([[webView subviews] count] > 0)
{
for (UIView* shadowView in [[[webView subviews] objectAtIndex:0] subviews])
{
[shadowView setHidden:YES];
}
// unhide the last view so it is visible again because it has the content
[[[[[webView subviews] objectAtIndex:0] subviews] lastObject] setHidden:NO];
}
There is a private method with the selector setAllowsRubberBanding: that takes a BOOL value. If passed NO, you will not be able to scroll the web view past the top or bottom of the content area, but will still let you scroll through the web view normally. Unfortunately, this method IS private, and your app will likely not be allowed onto the store if you use it.
You could, however, potentially try and extract the method implementation and bind it to a different selector that you've created, using the dynamic nature of Objective-C's runtime.
Still, the method is private and may no longer exist in future versions of the OS. If you still want to try, here's some sample code that will extract the setAllowsRubberBanding: method implementation and call it for you.
static inline void ShhhDoNotTellAppleAboutThis (UIWebView *webview)
{
const char *hax3d = "frgNyybjfEhooreOnaqvat";
char appleSelName[24];
for (int i = 0; i < 22; ++i)
{
char c = hax3d[i];
appleSelName[i] = (c >= 'a' && c <= 'z') ? ((c - 'a' + 13) % 26) + 'a' : ((c - 'A' + 13) % 26) + 'A';
}
appleSelName[22] = ':';
appleSelName[23] = 0;
SEL appleSEL = sel_getUid(appleSelName);
UIScrollView *scrollView = (UIScrollView *)[webview.subviews objectAtIndex:0];
Class cls = [scrollView class];
if (class_respondsToSelector(cls, appleSEL) == NO)
{
return;
}
IMP func = class_getMethodImplementation(cls, appleSEL);
func(scrollView, appleSEL, NO);
}
Please note that this will probably still get caught by Apple's static analyzer if you choose to submit an app using this code to the AppStore.
Here is a Swift function that gets rid of the shadow in a UIWebView in iOS 9. It’s safer than any alternative I’ve seen on SO because everything in it is in Apple documentation, and it specifically alters the shadow property (as opposed to hiding the entire view or some other property of the view).
func removeShadow(webView: UIWebView) {
for subview:UIView in webView.scrollView.subviews {
subview.layer.shadowOpacity = 0
for subsubview in subview.subviews {
subsubview.layer.shadowOpacity = 0
}
}
}
You can always access the subviews property of a UIView(documentation). Every UIView has a layer property that is a CALayer (documentation). Every CALayer has shadowOpacity (documentation).
Caveats:
You might have to go deeper in navigating the view hierarchy through subviews depending on your situation.
This works as long as you don’t want any shadows anywhere in the web view controller. If you have a view where you want to keep the shadow (other than the default UIWebView shadow), then you could add an if-check to identify that view and not set that view’s layer’s shadowOpacity to zero.
According to Apple “For complex views declared in UIKit and other system frameworks, any subviews of the view are generally considered private and subject to change at any time. Therefore, you should not attempt to retrieve or modify subviews for these types of system-supplied views. If you do, your code may break during a future system update” . . . in other words, UIWebView can change and its not recommended to be digging into these subviews. However, digging into the UIWebView is the only way to get rid of the shadow and this is a relatively safe way to do it.
This can be done without use of private APIs. All you need to do is hide each UIImageView with the shadow in it. Heres the code:
for (int x = 0; x < 10; ++x) {
[[[[[webView subviews] objectAtIndex:0] subviews] objectAtIndex:x] setHidden:YES];
}
Try this
func webViewDidFinishLoad(_ webView: UIWebView) {
for shadowView in self.webView.scrollView.subviews {
if !shadowView.isKind(of: UIImageView.self) {
shadowView.subviews[0].layer.shadowColor = UIColor.clear.cgColor
} else {
shadowView.layer.shadowColor = UIColor.clear.cgColor
}
}
}
Traverse all subviews, the UIImageViews whose image is only 1 pixel wide are shadow images, you can hide them.
- (void)hideShadows {
[webview traverseViewsWithBlock:^(UIView *view) {
UIImageView *imgView = ([view isKindOfClass:[UIImageView class]] ? (UIImageView*)view : nil;
// image views whose image is 1px wide are shadow images, hide them
if (imgView && imgView.image.size.width == 1) {
imgView.hidden = YES;
}
}];
}
traverseViewsWithBlock does what it looks like:
- (void)traverseViewsWithBlock:(void (^)(UIView* view))block
{
block(self);
for (id subview in self.subviews) {
[subview traverseViewsWithBlock:block];
}
}
I looked at the class properties and didn't find anything there but I can think of two "cover up" strategies:
1. You can use another view (parent of the web view) to clip the webview bounds.
2. You can add another view on top of the webview to cover the needed area with a color that matches the background, you can use an uiimage with a transparent area in the center.
By the way I don't like this standard background of the table views :P, but changing it can be a pain in the ass :P
You have to be careful, the scroll indicators are UIImageViews as well.
I'll improve my code, but here's a basic subclassed solution:
http://forrst.com/posts/A_tiny_UIWebView_hack_remove_shadows_from_behi-gzH
The easiest way to hide scroll indicators and transparent the web view here in UIWebView
To remove the scrolls.
for(UIView *view in webView.subviews){ 
     if ([view isKindOfClass:[UIScrollView class]]) {
          UIScrollView *sView = (UIScrollView *)view;
          //to hide verticalScroller
          sView.showsVerticalScrollIndicator = NO;
sView.showsVerticalScrollIndicator = NO;
     }
}
What about a category on UIWebView like this:
- (BOOL)showsScrollShadows
{
for(UIImageView *imageView in [self imageViewsWithShadows])
{
if(imageView.hidden)
{
return NO;
}
break;
}
return YES;
}
- (void)setShowsScrollShadows:(BOOL)showsScrollShadows
{
[[self imageViewsWithShadows] makeObjectsPerformSelector:#selector(setHidden:) withObject:#(!showsScrollShadows)];
}
- (NSArray *)imageViewsWithShadows
{
NSArray *potentialShadowImageViews = (self.subviews.count > 0) ? [self.subviews[0] subviews] : nil;
if(potentialShadowImageViews.count > 0)
{
NSPredicate *predicate = [NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings)
{
return [evaluatedObject isKindOfClass:[UIImageView class]];
}];
return [potentialShadowImageViews filteredArrayUsingPredicate:predicate];
}
return nil;
}
I've had a look around and can't see anything related to it. Apart from masking it with a view or clipping it somehow, the only thing I can think of is to loop through all of the UIWebView subviews (and sub-subviews etc.) and see if you can see anything there!
I may be wrong, but I think the shadow only shows up when we scroll the webview doesn't it ?
In that case, do you want to prevent the scrolling or really hide the shadow ? I don't know any tips that would hide the shadow. To disable the scrolling, I would setUserInteractionEnabled to NO.
I added a recursive method as a category to the UIView object so that it will do a depth-first walk of the subviews of the method's receiving view, hiding any UIImageView subclasses it finds. It will not crash if there are no subviews. The -apply: method is from BlocksKit. You could rewrite this function not to use it, but the block is applied in parallel to each element of the receiving array, so it's pretty fast.
#implementation UIView (RemoveShadow)
- (void)removeShadow {
if (self.subviews.count == 0 && [self isKindOfClass:[UIImageView class]]) {
self.hidden = YES;
} else if (self.subviews.count > 0) {
[self.subviews apply:^(id sender) {
[(UIView *)sender removeShadow];
}];
}
}
#end
if (UIDevice.currentDevice.systemVersion.intValue < 7)
for (UIImageView *imageView in webView.scrollView.subviews)
if ([imageView isKindOfClass:[UIImageView class]] && imageView.image.size.width == 1)
imageView.hidden = YES;