10

So, I'm having issues in a project where certain strings are not being rendered properly in some old iPhones with certain iOS versions (specifically it does not work for iPhone 5 with iOS10, while it does for iOS9.3, oddly enough). To reduce the issue, I wrote this code:

import UIKit

class ViewController: UIViewController {
    @IBOutlet weak var label: UILabel!

    override func viewDidLoad() {
        super.viewDidLoad()

        // Long HTML, although we'll make it even larger to prove a point
        let string = "<h1>Thing 1</h1><p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. </p><h1>Thing 2</h1><p>Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. </p><h1>Thing 3</h1><p>Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. </p><h1>Thing 4</h1><p>Sed lectus. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. </p><h1>Thing 5</h1><p>Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. </p><h1>Thing 6</h1><p>Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. </p>"

        let attrStr = try! NSAttributedString(
            data: (string + string + string + string + string + string + string + string).data(using: String.Encoding.unicode,allowLossyConversion: true)!,
            options: [ NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType],
            documentAttributes: nil)

        label.attributedText = attrStr
        label.backgroundColor = UIColor(red:0.00, green:1.00, blue:0.00, alpha:1.0)
    }
}

Simple enough. Of course the real issue is more complicated than this, but the idea is the same.

So, in an iPhone 7, all works fine:

enter image description here

However, when loading it on an iPhone 4s:

enter image description here

When debugging, attrStr seems to be properly set in both cases.

Any ideas? My theory is that older phones are too slow to render this on time, but I'm not quite sure how to workaround this.

Thanks!

EDIT: For those that asked, this is the Storyboard:

<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="11201" systemVersion="16A323" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="JzW-qG-LFG">
    <dependencies>
        <deployment identifier="iOS"/>
        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="11161"/>
        <capability name="Constraints to layout margins" minToolsVersion="6.0"/>
        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
    </dependencies>
    <scenes>
        <!--View Controller-->
        <scene sceneID="9PR-d2-hNU">
            <objects>
                <viewController id="JzW-qG-LFG" customClass="ViewController" customModule="things" customModuleProvider="target" sceneMemberID="viewController">
                    <layoutGuides>
                        <viewControllerLayoutGuide type="top" id="VGp-YT-QQN"/>
                        <viewControllerLayoutGuide type="bottom" id="SeT-t7-CD0"/>
                    </layoutGuides>
                    <view key="view" contentMode="scaleToFill" id="gtg-CO-E9d">
                        <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
                        <subviews>
                            <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Id1-A1-RaT">
                                <fontDescription key="fontDescription" type="system" pointSize="17"/>
                                <nil key="textColor"/>
                                <nil key="highlightedColor"/>
                            </label>
                        </subviews>
                        <color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
                        <constraints>
                            <constraint firstAttribute="trailingMargin" secondItem="Id1-A1-RaT" secondAttribute="trailing" id="0qp-Rk-PC2"/>
                            <constraint firstItem="Id1-A1-RaT" firstAttribute="leading" secondItem="gtg-CO-E9d" secondAttribute="leadingMargin" id="3Vc-he-HqH"/>
                            <constraint firstItem="Id1-A1-RaT" firstAttribute="top" secondItem="VGp-YT-QQN" secondAttribute="bottom" id="7IE-fY-4H0"/>
                        </constraints>
                    </view>
                    <navigationItem key="navigationItem" id="Opz-px-hsl"/>
                    <connections>
                        <outlet property="label" destination="Id1-A1-RaT" id="TNv-G9-ASE"/>
                    </connections>
                </viewController>
                <placeholder placeholderIdentifier="IBFirstResponder" id="qlV-6d-ISJ" userLabel="First Responder" sceneMemberID="firstResponder"/>
            </objects>
            <point key="canvasLocation" x="2848.8000000000002" y="-280.20989505247377"/>
        </scene>
    </scenes>
</document>
rob mayoff
  • 375,296
  • 67
  • 796
  • 848
pyriku
  • 1,251
  • 7
  • 17
  • 3
    It's considered bad practice to post a question, then delete it and repost again another day. – rmaddy Nov 27 '16 at 18:37
  • Are you sure the `UILabel` that you use to show your string is properly displayed on the smaller screen iPhone 4s? Could it be that because of some constraints, the label's frame is being shifted such that it is not displayed on-screen/ too small to display the attributed string? – Aleksander Nov 27 '16 at 18:53
  • @rmaddy I am well aware of that, but the question got removed because I used Samuel L. Ipsum and was considered obscene. Got asked to post it again with more appropiate content – pyriku Nov 27 '16 at 19:04
  • @Aleksander yes, that is not the problem at all, as in the iPhone 7 the content is scrollable, and if the string is slightly shorter (but still not fitting the screen) it will appear – pyriku Nov 27 '16 at 19:05
  • maybe the problem is in the constraints? – DeyaEldeen Nov 27 '16 at 19:27
  • Could you check `label` frame? Because since you set the background color to green, but it's not even visible on the 4S case. – Larme Nov 28 '16 at 08:56
  • @DeyaEldeen Constraints are OK, they're not the problem – pyriku Nov 28 '16 at 22:35
  • Is the label in a scroll view by any chance? An issue could be that the contentSize of the scroll view is not getting updated correctly, so even if all the frames look correct, it still won't render correctly – bobDevil Nov 29 '16 at 23:38
  • @pyriku unfortunately I had the same issue and never found a consistent way to reproduce it. http://stackoverflow.com/questions/28316899/nsattributedstring-initwithdataoptionsdocumentattributeserror-painfully-slow. But to avoid any other issue I suggest to inspect views by using 3d mode – Andrea Nov 30 '16 at 08:22

3 Answers3

13

The problem here is that UIView has an undocumented height limit of 8192 points. Once the label is 8192 points tall (or taller), it no longer updates the frame buffer. (Presumably the window server simply ignores the label's CALayer once the layer is too large.) Whatever was already in the frame buffer remains there. Presumably the behavior is undefined and may vary across devices and versions of iOS.

(Edit: Having thought about this more, I suspect the limit is 16,384 pixels, not 8,192 points. I don't think the window server deals in points.)

To demonstrate, I took your code and storyboard and added three things:

  1. I constrained the height of your label to be less than or equal to 8185 points.
  2. I added a slider that updates the constant of that height constraint. The slider allows the range 8185 to 8200.
  3. I added another label that displays the constant of the height constraint.

Here's what happens in the iPhone SE simulator running iOS 10.2:

demo of 8192 point height problems

In the demo, you can see that as soon as the height limit crosses 8192 points, the thumb of the slider starts to look smeared, and the height label's text visibly overwrites itself. This is because the green label is no longer updating the frame buffer, so whatever was previously there (as drawn by the slider and the height label) remains, only overwritten where the slider and the height label redraw themselves. As soon as the height goes back down across the 8192 point threshold, the green label draws itself again, cleaning up the mess.

I think you're not seeing this in your iPhone 7 test because the iPhone 7 has a wider screen than the iPhone SE. All iPhones with 3.5 inch and 4 inch screens are 320 points wide. iPhones with 4.7 inch screens are 375 points wide, and iPhones with 5.5 inch screens are 414 points wide.

A wider screen means a wider UILabel. That means more text fits on each line, so the label doesn't have to be as tall to fit all the text. I suspect that on the larger screens, your labels are shorter than 8192 points so they don't hit undefined behavior.

Workarounds:

  • If you don't intend to let the user scroll to see the entire text, just put a height constraint on the label.

  • If you want to let the user scroll, try using a UIWebView or WKWebView instead of a UILabel to show the text. I believe these views can handle content more than 8192 points tall.

Incidentally, you're not the first person to run into this problem: UILabel view disappear when the height greater than 8192. However, your question currently has an open bounty, which prevents it from being closed as a duplicate.

Community
  • 1
  • 1
rob mayoff
  • 375,296
  • 67
  • 796
  • 848
1

You haven't provided any details about your Storyboard, or the settings or constraints for your label.

So I have initiated the label programmatically, and this solution works on the iPhone 4s running iOS 9.3:

enter image description here

Here is the working source code:

override func viewDidLoad() {
    super.viewDidLoad()

    label = UILabel(frame: CGRect(x: 0.0, y: 44.0, width: view.frame.size.width, height: view.frame.size.height - 88.0))
    label.numberOfLines = 0
    label.isUserInteractionEnabled = false
    label.contentMode = .left
    label.textAlignment = .natural
    label.lineBreakMode = .byTruncatingTail
    label.baselineAdjustment = .alignBaselines
    label.adjustsFontSizeToFitWidth = false
//  label.translatesAutoresizingMaskIntoConstraints = false //setting this causes the text to be layout wrong
    label.autoresizingMask = [UIViewAutoresizing.flexibleWidth, UIViewAutoresizing.flexibleHeight]

    view.addSubview(label)

    // Long HTML, although we'll make it even larger to prove a point
    let string = "<h1>Thing 1</h1><p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. </p><h1>Thing 2</h1><p>Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. </p><h1>Thing 3</h1><p>Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. </p><h1>Thing 4</h1><p>Sed lectus. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. </p><h1>Thing 5</h1><p>Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. </p><h1>Thing 6</h1><p>Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. </p>"

    let attrStr = try! NSAttributedString(
        data: (string + string + string + string + string + string + string + string).data(using: String.Encoding.unicode,allowLossyConversion: true)!,
        options: [ NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType],
        documentAttributes: nil)

    label.attributedText = attrStr
    label.backgroundColor = UIColor(red:0.00, green:1.00, blue:0.00, alpha:1.0)
}

A complete working project may be downloaded from here.

Jeshua Lacock
  • 5,730
  • 1
  • 28
  • 58
  • Thanks, that works, but I don't understand why! I edited my answer with my storyboard. The only constraints in that label are left, right and top, and the number of lines is 0, so it should extend. – pyriku Nov 30 '16 at 08:16
  • I wasn't able to open your storyboard in Xcode, but I applied all of the settings (except the constraints) to my code. There is one setting `translatesAutoresizingMaskIntoConstraints` that caused the formatting to be incorrect, so I commented it out. – Jeshua Lacock Nov 30 '16 at 09:35
  • You said it works, so I am left wondering why you haven't accepted my solution? – Jeshua Lacock Nov 30 '16 at 21:37
  • 1
    I am just trying to extrapolate this to the bigger picture of the project I'm facing this issue, and I'm not sure how viable this solution is just yet. – pyriku Dec 01 '16 at 08:15
  • Well, in all fairness, your question you didn't state that the label **must** be initiated in a Storyboard. What do you think isn't *viable* about it? Its perfectly valid to initiate objects yourself. – Jeshua Lacock Dec 01 '16 at 08:59
  • As you can imagine, from a big project with a big codebase, making these sort of changes may not be at all viable. It'd be great to find a solution using the storyboards if possible! Relax, if your solution fits the bill, you'll get the internet points. – pyriku Dec 01 '16 at 13:40
  • I disagree. I develop insanely complicated projects and you can either maintain an object in a Storyboard or add a few lines of code to initialize a `UILabel`. I don't see how adding a few lines of code add is any more complicated than a Storyboard counterpart. Its just a UILabel. – Jeshua Lacock Dec 02 '16 at 03:02
  • 2
    The reason this code “fixes” the problem is that this code sets the height of the label to a fixed size. In pyriku's version, autolayout sets the height of the label to its `intrinsicContentSize.height`, and on some devices, that height crosses an undocumented limit. – rob mayoff Dec 04 '16 at 08:16
1

You are most definitely right. This is a time issue.

NSHTMLTextDocumentType of NSDocumentTypeDocumentAttribute is notorious for taking very long times. As you increase the attrStr string length (adding a couple of + string in data ) you'll be able to replicate the bug for higher end devises after a certain string length. I can reproduce this in an iPhone 6 plus emulator using iOS 10.1, since emulator works with less processing power than the actual devise.

If it's viable to you, you may try using DTCoreText to solve this issue. This library handles html string with methods other than the built in ones, reducing the rendering times. Which will make the bug less noticeable in lower end mobiles.

Community
  • 1
  • 1
Penkey Suresh
  • 5,816
  • 3
  • 36
  • 55
  • 1
    This is not a time issue. No matter how long you wait, the text won't show up. You can reproduce this in the simulator and you'll see that the simulator doesn't use any CPU time. – rob mayoff Dec 04 '16 at 08:14