How to clear font cache filled with emoji characters?

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.

  • Improve HTML5 Canvas frame by frame JPG animation to fully cache before animating
  • Truncate string containing emoji or unicode characters at word or character boundaries
  • When are files from NSCachesDirectory removed?
  • SDURLCache with AFNetworking and offline mode not working
  • NSString to Emoji Unicode
  • Sending Emoji in Push Notifications via PHP on iOS
  • 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?


    Edit

    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:
    enter image description here

    4 Solutions Collect From Internet About “How to clear font cache filled with emoji characters?”

    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.)

    Many emojis are represented by sequences that contain more than one unicode scalar. Matthew’s answer works well with basic emojis but it returns only first scalar from the sequences of emojis like country flags.

    The code below will get full sequences and create a string that matches gemoji exported file names.

    Some simple smiley emojis also have the fe0f selector. But gemoji doesn’t add this selector to file names on exporting, so it should be removed.

    func emojiToHex(_ emoji: String) -> String
    {
        var name = ""
    
        for item in emoji.unicodeScalars {
            name += String(item.value, radix: 16, uppercase: false)
    
            if item != emoji.unicodeScalars.last {
                name += "-"
            }
        }
    
        name = name.replacingOccurrences(of: "-fe0f", with: "")
        return name
    }