I am developing keyboard extension for iPhone. There is an emoji screen smilar to Apples own emoji keyboard that shows some 800 emoji characters in UICollectionView
.
When this emoji UIScrollView
is scrolled the memory usage increases and does not drop down. I am reusing cells correctly and when testing with single emoji character displayed 800 times the memory does not increase during scrolling.
Using instruments I found that there is no memory leak in my code but it seems that the emoji glyphs are cached and can take around 10-30MB of memory depending on font size (reseach shows they are actually PNGs). Keyboard extensions can use little memory before they are killed. Is there a way to clear that font cache?
Adding code example to reproduce the problem:
let data = Array("๐๐โบ๏ธ๐๐๐๐๐๐๐๐๐๐ณ๐๐๐๐๐๐ฃ๐ข๐๐ญ๐ช๐ฅ๐ฐ๐
๐๐ฉ๐ซ๐จ๐ฑ๐ ๐ก๐ค๐๐๐๐ท๐๐ด๐ต๐ฒ๐๐ฆ๐ง๐๐ฟ๐ฎ๐ฌ๐๐๐ฏ๐ถ๐๐๐๐ฒ๐ณ๐ฎ๐ท๐๐ถ๐ฆ๐ง๐จ๐ฉ๐ด๐ต๐ฑ๐ผ๐ธ๐บ๐ธ๐ป๐ฝ๐ผ๐๐ฟ๐น๐พ๐น๐บ๐๐๐๐๐ฝ๐ฉ๐ฅโจ๐๐ซ๐ฅ๐ข๐ฆ๐ง๐ค๐จ๐๐๐๐
๐๐๐๐๐โโ๏ธ๐โ๐๐๐๐๐๐๐โ๏ธ๐๐ช๐ถ๐๐๐ซ๐ช๐ฌ๐ญ๐๐๐ฏ๐๐
๐๐๐๐๐
๐ฐ๐๐๐๐ถ๐บ๐ฑ๐ญ๐น๐ฐ๐ธ๐ฏ๐จ๐ป๐ท๐ฝ๐ฎ๐๐ต๐๐ด๐๐๐ผ๐ง๐ฆ๐ค๐ฅ๐ฃ๐๐๐ข๐๐๐๐๐๐๐๐ ๐๐ฌ๐ณ๐๐๐๐๐๐
๐๐๐๐๐๐๐๐๐๐ฒ๐ก๐๐ซ๐ช๐๐๐ฉ๐พ๐๐ธ๐ท๐๐น๐ป๐บ๐๐๐๐ฟ๐พ๐๐ต๐ด๐ฒ๐ณ๐ฐ๐ฑ๐ผ๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐ โญ๏ธโ๏ธโ
๏ธโ๏ธโก๏ธโ๏ธโ๏ธโ๏ธ๐๐๐๐โ๏ธ๐ต๐ถ๐ผ๐บ๐ป๐ธ๐น๐ท๐ด๐๐๐๐๐๐๐๐ค๐ฑ๐ฃ๐ฅ๐๐๐๐๐ฒ๐ข๐ก๐ณ๐๐ฉ๐ฎ๐ฆ๐จ๐ง๐๐ฐ๐ช๐ซ๐ฌ๐ญ๐ฏ๐๐๐๐๐๐๐๐๐๐๐๐๐๐ ๐๐
๐ฝ๐๐๐๐๐๐๐๐๐๐๐๐ป๐
๐๐๐๐๐๐๐๐ฎ๐๐๐๐โค๏ธ๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐ก๐ ๐ข๐๐๐๐๐ฝ๐๐๐๐ผ๐๐๐๐๐๐๐๐๐๐ฌ๐ญ๐ฐ๐จ๐ฌ๐ฉ๐ช๐ญ๐ค๐ง๐ผ๐ต๐ถ๐น๐ป๐บ๐ท๐ธ๐พ๐ฎ๐๐ด๐๏ธ๐ฒ๐ฏ๐๐โฝ๏ธโพ๏ธ๐พ๐ฑ๐๐ณโณ๏ธ๐ต๐ด๐๐๐๐ฟ๐๐๐๐ฃ").map {String($0)}
class CollectionViewTestController: UICollectionViewController {
override func viewDidLoad() {
collectionView?.registerClass(Cell.self, forCellWithReuseIdentifier: cellId)
}
override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return data.count
}
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(cellId, forIndexPath: indexPath) as! Cell
if cell.label.superview == nil {
cell.label.frame = cell.contentView.bounds
cell.contentView.addSubview(cell.label)
cell.label.font = UIFont.systemFontOfSize(34)
}
cell.label.text = data[indexPath.item]
return cell
}
override func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
return 1
}
}
class Cell: UICollectionViewCell {
private let label = UILabel()
}
After running and scrolling the UICollectionView
I get memory usage graph like this:
I ran into the same issue and fixed it by dumping the .png from /System/Library/Fonts/Apple Color Emoji.ttf and using UIImage(contentsOfFile: String) instead of a String.
I used https://github.com/github/gemoji to extract the .png files, renamed the files with @3x suffix.
func emojiToHex(emoji: String) -> String { let data = emoji.dataUsingEncoding(NSUTF32LittleEndianStringEncoding) var unicode: UInt32 = 0 data!.getBytes(&unicode, length:sizeof(UInt32)) return NSString(format: "%x", unicode) as! String } let path = NSBundle.mainBundle().pathForResource(emojiToHex(char) + "@3x", ofType: "png") UIImage(contentsOfFile: path!)
UIImage(contentsOfFile: path!) is properly released so the memory should stay at a low level. So far my keyboard extension hasn't crashed yet.
If the UIScrollView contains a lot of emoji, consider using UICollectionView that retains only 3 or 4 pages in cache and releases the other unseen pages.
I had the same issue and tried many things to release the memory, but no luck. I just changed the code based on Matthew's suggestion. It works, no more memory problem for me including iPhone 6 Plus.
The code change is minimal. Find the change in the UILabel subclass below. If you ask me the challenge is to get the emoji images. I could not figure how gemoji (https://github.com/github/gemoji) works out yet.
//self.text = title //what it used to be
let hex = emojiToHex(title) // this is not the one Matthew provides. That one return strange values starting with "/" for some emojis.
let bundlePath = NSBundle.mainBundle().pathForResource(hex, ofType: "png")
// if you don't happened to have the image
if bundlePath == nil
{
self.text = title
return
}
// if you do have the image
else
{
var image = UIImage(contentsOfFile: bundlePath!)
//(In my case source images 64 x 64 px) showing it with scale 2 is pretty much same as showing the emoji with font size 32.
var cgImage = image!.CGImage
image = UIImage( CGImage : cgImage, scale : 2, orientation: UIImageOrientation.Up )!
let imageV = UIImageView(image : image)
//center
let x = (self.bounds.width - imageV.bounds.width) / 2
let y = (self.bounds.height - imageV.bounds.height) / 2
imageV.frame = CGRectMake( x, y, imageV.bounds.width, imageV.bounds.height)
self.addSubview(imageV)
}
The emojiToHex() method Matthew provides returns strange values starting with "/" for some emojis. The solution at the given link work with no problems so far. Convert emoji to hex value using Swift
func emojiToHex(emoji: String) -> String
{
let uni = emoji.unicodeScalars // Unicode scalar values of the string
let unicode = uni[uni.startIndex].value // First element as an UInt32
return String(unicode, radix: 16, uppercase: true)
}
---------- AFTER SOME TIME----
It turned out this emojiToHex method does not work for every emoji. So I end up downloading all emojis by gemoji and map each and every emoji image file (file names are like 1.png, 2.png, etc) with the emoji itself in a dictionary object. Using the following method instead now.
func getImageFileNo(s: String) -> Int
{
if Array(emo.keys).contains(s)
{
return emo[s]!
}
return -1
}
I am guessing that you are loading the images using [UIImage imageNamed:]
, or something that derives from it. That will cache the images in the system cache.
You need to load them using [UIImage imageWithContentsOfFile:]
instead. That will bypass the cache.
(And if that's not the problem, then you'll need to include some code in your question so that we can see what's happening.)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With