JPG image doesn't load with UIImage imageNamed - ios

I read on the reference that from iOS version 4.+ with the method imageNamed of UIImage object, the file extension is not required.
From UIImage class reference:
Special Considerations.
On iOS 4 and later, the name of the file is not required to specify
the filename extension. Prior to iOS 4, you must specify the filename
extension.
But it seems that this only work with PNG files.
If my code is:
[UIImage imageNamed:#"test"];
The image loads only if the file is test.png
The image doesn't load if it's test.jpg.
For me it is a big problem because I need to maintain a dynamic image loading (I do not know at runtime if the image I want to load is png or jpg).
Please can you help me?
Thanks.

The latest developer reference states the missing piece of information:
Special Considerations
On iOS 4 and later, if the file is in PNG format, it is not necessary to specify the .PNG filename extension. Prior to iOS 4, you must specify the filename extension.
One possible reason that the library gives PNGs special treatment, is that the iOS hardware is optimized for PNGs. PNG images stored in the application bundle are optimized by Xcode, changing the byte order of the PNG images to match the graphics chip of the iPhone device. (see this question: Is PNG preferred over JPEG for all image files on iOS?).
If you know that you will only have a PNG or a JPG, an alternative solution is to create a category on UIImage as per below.
- (UIImage*) jgcLoadPNGorJPGImageWithName:(NSString*)name {
UIImage * value;
if (nil != name) {
value = [UIImage imageNamed:name];
if (nil == value) {
NSString * jpgName = [NSString stringWithFormat:#"%#.jpg", name];
value = [UIImage imageNamed:jpgName];
}
}
return value;
}

If you have these images bundled in your app, you SHOULD know their extensions.
If you're getting them from an online source and you have them as NSData, you can use this code here to determine the type.
+ (NSString *)contentTypeForImageData:(NSData *)data {
uint8_t c;
[data getBytes:&c length:1];
switch (c) {
case 0xFF:
return #"image/jpeg";
case 0x89:
return #"image/png";
case 0x47:
return #"image/gif";
case 0x49:
case 0x4D:
return #"image/tiff";
}
return nil;
}
As per the top answer in this question.

Related

Flutter: Picking images as jpg/png instead of heic on iOS

How can I pick images directly as jpg or png on iOS?
If this feature isn't available: How can I convert it very fast and don't have to wait a long time?
Edit: I want to prevent picking .heic because I have to send it to an server, which handles jpg and png and not .heic
Thanks to Mohammad Assad Arshad!
It can be solved by pub.dev/packages/file_picker.
An additional explanation:
https://pub.dev/packages/file_picker allows you to pick files/images etc. and the file type of images is .jpg
https://pub.dev/packages/multi_image_picker allows you to pick images too, but takes the original file format; e.g. .heic, .jpg, .png
i select images with filepicker https://pub.dev/packages/file_picker
FilePickerResult result;
try {
result = await FilePicker.platform.pickFiles(
type: FileType.custom,
allowedExtensions: ['jpeg', 'jpg', 'heic', 'pdf'],
);
} catch (e) {
print('Exep: ****${e}***');
}
now you can check the extention of the file, use the package path.dart as p and package https://pub.dev/packages/heic_to_jpg to convert image to jpeg
File file = File(result.files.first.path);
String fileExtension = p.extension(file.path).replaceAll('.', '');
if (fileExtension == 'heic') {
print('convert to jpeg');
String jpegPath = await HeicToJpg.convert(file.path);
file = File(jpegPath);
fileExtension = 'jpeg';
}
do not forget do the same if you use imagePicker with source camera.
you can use multi_image_picker 4.6.7 to pick image in iOS for HEIC images.

iOS 13.2 MKTileOverlay occasionally won't render

I'm having an issue where in iOS 13.2 (probably also from iOS 13), loading offline map tile using MKTileOverlay occasionally won't be able to render, leaving the tile blank, there seems to be no issue with MKTileOverlay's subclass at all as it worked well in iOS 12 and below. I have 2 MKTileOverlay class (1 add grid and 1 load map tile file, default MKTileOverlay), both won't be able to load on that blank tile with default MKTileOverlayRenderer, other overlays seems to appear fine.
The issue seems to be resolved itself if I go to home screen and go back to the app, causing the tiles to reload. Is this a bug from iOS MapKit itself? Does anyone have temporary solution for this? Thank you.
Code for adding overlay:
let overlay = MKTileOverlay(urlTemplate: urlTemplate)
overlay.canReplaceMapContent = true
overlay.maximumZ = 19
mapView.insertOverlay(overlay, at: 0, level: .aboveLabels)
Renderer:
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
if overlay is MKTileOverlay {
let renderer = MKTileOverlayRenderer(tileOverlay: overlay as! MKTileOverlay)
return renderer
}
return MKOverlayRenderer()
}
It is clearly a MapKit issue/bug.
I've also open a feedback ticket since the 9th of December 2020.
The root of this issue is not very sure.
MapKit and specially MKTileOverlay always had/have some issues with "heavy" tiles like PNG 24bit. When the MKTileOverlay use PNG (heavy tiles), the tiles sometimes are flashing and the map is continuously reloading especially with wide screens (iPad pro etc..)
So, since the JPEG tiles are often lighter than PNG, JPEG can be a workaround.
BUT, this new iOS 13.2+ issue is not the same! Random tiles are not rendered. If you remove and readd the MKTileOverlay or call the reloadData method of MKTileOverlayRenderer, the missing tiles will be rendered and it will be other random tiles that which be missing.
The real solution of the issue is to open a feedback ticket: https://feedbackassistant.apple.com
Edit: I've just tried to replace my 8bit PNG by 85% JPEG on the very simple MKTileOverlay project sample i've sent to Apple in my ticket. Same issue... no improvement.
Edit 2: Loading the NSData into an UIImage then using UIImageRepresentationJPEG seems to do the trick... Ugly...
- (void)loadTileAtPath:(MKTileOverlayPath)path result:(void (^)(NSData * _Nullable, NSError * _Nullable))result
{
NSString *tilePath = [self PATHForTilePath:path];
NSData *data = nil;
if (![[NSFileManager defaultManager] fileExistsAtPath:tilePath])
{
NSLog(#"Z%ld/%ld/%ld does not exist!", path.z, path.x, path.y);
}
else
{
NSLog(#"Z%ld/%ld/%ld exist", path.z, path.x, path.y);
UIImage *image = [UIImage imageWithContentsOfFile:tilePath];
data = UIImageJPEGRepresentation(image, 0.8);
// Instead of: data = [NSData dataWithContentsOfFile:tilePath];
if (data == nil)
{
NSLog(#"Error!!! Unable to read an existing file!");
}
}
dispatch_async(dispatch_get_main_queue(), ^{
result(data, nil);
});
}
As I noted in a comment to the original question I was having the same problem, but it is now largely resolved, so I thought I'd post what worked for me.
The problem for me occurred in the following method
- (void)loadTileAtPath:(MKTileOverlayPath)path result:(void (^)(NSData * __nullable tileData, NSError * __nullable error))result{
where the custom tile would fail to load even when it was being presented with what appeared to be valid NSData.
I found that the problem was reduced if I used jpegs instead of pngs for my custom tiles, but it was only when I changed the way that I was handling the tile data did the problem largely go away. (I largely, because I still get the occasional unloaded tile, but I'd say it's 100x less often than I was getting them before).
The following method is my Xamarin.iOS implementation of it, but you should be able to see the principle for Swift or Objective C.
The key is the difference in the way the NSData is created. Instead of calling the UrlForTilePath method, I create a UIImage from the tile path and then use the UIImageJPEGRepresentation (AsJPEG in C#) to create the NSData.
public override void LoadTileAtPath(MKTileOverlayPath path, MKTileOverlayLoadTileCompletionHandler result)
{
//I was using this prior to ios 13.2
//NSUrl url = this.URLForTilePath(path);
//NSData tileData = NSData.FromFile(url.AbsoluteString);
//result(tileData, null);
//Now I use this
String folderPath = "tiles/" + path.Z + "/" + path.X + "/";
String tilePath = NSBundle.MainBundle.PathForResource(path.Y.ToString(), "jpg", folderPath);
String blankPath = NSBundle.MainBundle.PathForResource("tile", "jpg");
try
{
//does the file exist?
UIImage tile;
if (File.Exists(tilePath))
{
tile = UIImage.FromFile(tilePath);
if (tile == null)
{
Console.WriteLine("Error Loading " + path.Z + " " + path.Y + " " + path.X);
//This may be redundant, as I'm not getting any errors here, even when the tile doesn't display
}
}
else
{
tile = UIImage.FromFile(blankPath);
}
NSData tileData = tile.AsJPEG();
result(tileData, null);
}
catch (Exception ex)
{
}
}

Is HEIC/HEIF Supported By UIImage

I was under the impression that UIImage would support HEIC/HEIF files introduced in iOS 11. In my testing that does not appear to be the case though. If I do let image = UIImage(named: "test") which points to test.heic then image is nil. If I use an image literal then it crashes the app. Wondering if this is not implemented yet for now. Thanks.
While Zhao's answer works, it is fairly slow. The below is about 10-20 times faster. It still doesn't work in the simulator for some reason though so keep that in mind.
func convert(url: URL) -> UIImage? {
guard let source = CGImageSourceCreateWithURL(url as CFURL, nil) else { return nil }
guard let cgImage = CGImageSourceCreateImageAtIndex(source, 0, nil) else { return nil }
return UIImage(cgImage: cgImage)
}
This is kind of outlined on page 141 from the slides of a WWDC session but wasn't super clear to me before: https://devstreaming-cdn.apple.com/videos/wwdc/2017/511tj33587vdhds/511/511_working_with_heif_and_hevc.pdf
Unfortunately I still haven't been able to figure out a way to use images in the xcassets folder so you'll either have to include the files outside of assets or pull from on the web. If anyone knows a way around this please post.
In Xcode 10.1 (10B61), UIImage(named: "YourHeifImage") works just like other assets.
Interestingly though, when you want to try this out …and you AirDrop a HEIF pic from your (e.g. iPhone) Photos to your mac, it will get the extension .HEIC (all caps). When you then add that image to your Xcode xcassets, Xcode complains about an incorrect extension:
….xcassets: warning: Ambiguous Content: The image set "IMG_1791" references a file "IMG_1791.HEIC", but that file does not have a valid extension.
If you first change the extension to the lower-case .heic, and then add it to xcassets, all is well.
You can load HEIF via CIImage, then convert to UIImage
CIImage *ciImage = [CIImage imageWithContentsOfURL:url];
imageView.image = [UIImage imageWithCIImage:ciImage];

Printing PDF on iOS, background image not printing

I'm attempting to print a PDF file in my Cordova application on iOS.
The file is generated using jsPDF in the Cordova app and then I've modified the katzer cordova-plugin-printer to accept the raw PDF data as a string, convert it to NSData and print it out.
- (void) printPDFFromData:(CDVInvokedUrlCommand*)command
{
if (!self.isPrintingAvailable)
{
return;
}
NSArray* arguments = [command arguments];
NSString* documentData = [arguments objectAtIndex:0];
NSData* pdfData = [documentData dataUsingEncoding:NSUTF8StringEncoding];
UIPrintInteractionController* controller = printController;
[self adjustSettingsForPrintController:controller];
controller.printingItem = pdfData;
[self openPrintController:controller];
[self commandDelegate];
}
Using the iOS print simulator (I don't have access to an AirPrint printer), the PDF appears to print out, except that the background image is not printed, just the vector drawings overlaying it.
The same raw output data when saved to a PDF file will display the background image and when you print that file, the background image is printed.
Is this just an anomaly of the printer simulator or do I need to somehow set the print controller to be able to print the image in the document?
I found a solution to the issue. Something was getting lost in the decoding of the string data from JavaScript into Objective-C.
To get around this I Base64 encoded the PDF document in my JS side before sending it off to the plugin:
var startIndexOfBase64Data = 28;
var base64Document = doc.output('dataurlstring').substring(startIndexOfBase64Data);
window.plugin.printer.printPDFFromData(base64Document);
Then I needed to add
NSData+Base64.m and NSData+Base64.h
from this sample project into my plugins directory to allow this line of code to convert the Base64 string into NSData:
NSData* pdfData = [NSData dataFromBase64String:documentData];
Then the document then printed out untainted.
Now I'm off to see if I can get it working with Android.

UIImage AsPNG and AsJPEG fails

I'm using MonoTouch and I have a UIImage (displayed in a UIImageView and it looks good) and I'm trying to convert it to NSData, but AsJPEG and AsPNG returns null. What can be the problem?
My code looks like this:
NSError err;
NSData imageData = CroppedImageView.Image.AsJPEG(); // imageData is null!
if (!imageData.Save ("tmp.png", true, out err)) {
Console.WriteLine("Saving of file failed: " + err.Description);
}
The AsJPEG method calls UIImageJPEGRepresentation and its return value is documented as:
A data object containing the JPEG data, or nil if there was a problem generating the data. This function may return nil if the image has no data or if the underlying CGImageRef contains data in an unsupported bitmap format.
The is similar to many API in iOS (and OSX) where exception are not commonly used (and null is used to report some kind of error).
Anyway you should check your image dimensions and properties - they might give you an hint at something that would not translate into a JPEG bitmap.
Also since the NSData can represent a very large amount of memory you should try to limit it's life, e.g.:
using (NSData imageData = CroppedImageView.Image.AsJPEG ()) {
NSError err;
if (!imageData.Save ("tmp.jpg", true, out err)) {
Console.WriteLine("Saving of file failed: " + err.Description);
}
}
It looks like you are writing to a file in the current directory of the app, this is readonly.
You should use:
var path = System.IO.Path.GetTempFilename();
or
var path = System.IO.Path.Combine(System.IO.Path.GetTempPath(), "tmp.png");
Like you would do on other platforms, and use a file from there.
You can also use Environment.SpecialFolder.MyDocuments.
The AsJPEG returned null because the image size was too big (it was taken with an iPhone 5). After I Scaled it down by 2, it generates the data properly.

Resources