Been really stumped by this problem. I’ve searched the web and haven’t found traces of info on it, but I believe it is a widespread issue.
See the HTML in this post. If I select more than 350 (the bounding value works for my iPhone, but it may vary based on device) photos the first 350 will upload just fine. For photos after 350 each one will fail. When this fails, the data returned is what appears to be an empty plist and the FileReader doesn’t error. Here is a sample of the bytes returned when it fails (appears to be an empty plist):
"bplist00�
X$versionY$archiverT$topX$objects��_NSKeyedArchiver� Troot��U$null$)27ILQSU["
So, the question is: Is this a known issue I just haven’t found Apple’s documentation for? Is there a way to work around this without putting an arbitrary limit on the number of files you can select?
The HTML is below. I hosted it somewhere and debugged from my iPhone tethered to my Mac. I am looking at the image header to avoid dumping out more bytes than necessary and overloading the debugger console. I only log the contents of the first failure, but believe that all failure have the same contents.
<html>
<head>
<title>Tester</title>
</head>
<body>
<button id="selectButton">Select Photos</button>
<input type="file" id="fileInput" style="display: none;" accept="image/*" multiple>
<script>
document.getElementById('selectButton').addEventListener('click', function() {
document.getElementById('fileInput').click();
});
document.getElementById('fileInput').addEventListener('change', function(event) {
const files = event.target.files;
for (let i = 0; i < files.length; i++) {
const file = files[i];
testImage(file);
}
});
var savedOutput = false;
function testImage(file) {
var _reader = new window.FileReader(),
_fn = file.slice || file.mozSlice || file.webkitSlice || function() {
return;
},
_slice = _fn.call(file, 0, 3);
_reader.onloadend = function(_evt) {
var _isValid = false,
_bytes;
if (!_evt.currentTarget.error) {
_bytes = new window.Uint8Array(_evt.currentTarget.result);
if (
(_bytes[0] == 0xFF && _bytes[1] == 0xD8 && _bytes[2] == 0xFF) || //Check for jpg
(_bytes[0] == 0x42 && _bytes[1] == 0x4D) || //Check for BMP
(_bytes[0] == 0x89 && _bytes[1] == 0x50 && _bytes[2] == 0x4E) //Check for png
) {
_isValid = true;
} else {
if (_bytes[0] == 0xFF && _bytes[1] == 0xD8 && _bytes[2] == 0xFF) {
_isValid = true;
}
}
if (_bytes[0] == 0x00 && _bytes[1] == 0x00 && _bytes[2] == 0x00) { //Check for heic
_isValid = true;
}
}
console.log('File: ' + file.name + ' is a JPEG: ' + _isValid);
if (savedOutput === false && _isValid === false) {
savedOutput = true;
const decoder = new TextDecoder();
const text = decoder.decode(_bytes);
console.log('Failed Output: ', text);
}
}
_reader.readAsArrayBuffer(file);
}
</script>
</body>
</html>
I have tried this from more involved applications and it always fails. The failure seems related to file access.
2
Answers
You are facing this problem which might be possible due to 2 main reasons. Firstly, mobile safari might have a memory limit for handling files and once that limit is reached it start giving incomplete data. Secondly, it might also be possible that file reader might not be optimized to handle large batches of files on mobile devices.
SO, what you can do is that insted of processing all the file together at the same time you can try to make it possible with throttling mechanism to limit the number of files being processed at the same time.
I have written one code which is working fine in my device. I am listing it below try it with your solution and in case of any doubts let me know.
Note: This is a JavaScript code so attach it with your html code.
Not sure but @Naveen might be onto something, however propsed solution is using
setTimeout
which might flood the memory slowly before GC kicks in. To correctly test the idea:I might have syntax errors, if in your browser
Promise.withResolvers()
does not exist, use the old school method of asigning resolver to a variable in the upper scope.