27

I originally asked this question. I had assumed that the reason for the slow load time of my custom view was because of layering multiple views on top of each other or perhaps because of some recursion problem. However, after cutting out more and more code to see what would make a difference, it came down to whether I had a UITextView present or not. Because the apparent source of my problem is so different than what I was expecting in my first question, I decided to start a new question rather than adding a lengthy update to the old one.

I set up my test project with two view controllers. A button on the first view controller calls a show segue to the second view controller. The second view controller has my custom view on it. (Using the second view controller let me get a sense of how long it took to load the custom view.)

Custom view code:

import UIKit

@IBDesignable class UIMongolTextView: UIView {

    var textView = UITextView() // key line

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }

    override init(frame: CGRect){
        super.init(frame: frame)
    }
}

As you can see, the only difference from a UIView is that I added a UITextView property. And it is this custom view that loads very slowly. Running the Allocations tool in Instruments I get the following results (a count of 997):

enter image description here

However, if I comment out the line

    //var textView = UITextView()

then I the custom view loads very quickly and only has a count of 7.

enter image description here

What is going on here? Is it possible to use a UITextView property in a custom view and avoid this slow load time?

Community
  • 1
  • 1
Suragch
  • 484,302
  • 314
  • 1,365
  • 1,393
  • I am having the same issue, but I am using a UIPageViewController, and there is a lag when trying to swipe to the next page because the text view is taking time to init. – Jacob Dec 17 '15 at 06:45
  • Note to self (to try later): What if I declared it as a `UIView` and then cast it to a `UITextView` later? – Suragch May 11 '16 at 05:06

4 Answers4

28

The bottleneck is the selectable property of your UITextView. There is an inexplicable performance issue in the creation of a UITextView with selectable set to its default value of true.

The easiest way to work around the problem is to add your text view using the storyboard, making sure that you untick the selectable property. There appears to be no documented way to create an unselectable text view in code (as setting selectable to false after creation does not avoid the performance issue during creation). If you need a selectable text view, first create an unselectable text view, and then set selectable to true in viewDidAppear.

If you can't use the storyboard, you might want to consider using a third party class such as TTTAttributedLabel.

It looks like Apple uses a private API to avoid this problem. Other enterprising developers have discovered that, in ChatKit, text views appear to be created using a private method called initReadonlyAndUnselectableWithFrame:textContainer:.

jamesk
  • 3,807
  • 21
  • 38
  • 2
    This is not the answer that I was hoping for, but it does answer the question (to some extent) of why this is happening. – Suragch Dec 19 '15 at 16:27
  • You should be able to do everything you need to do using a custom view controller, which comes with its own `view` property, and to which you can add a custom `textView` property connected to a UITextView in your storyboard. If you don't want the view to be full screen, use a "Container View" object in your storyboard to confine your custom view controller to a particular region. Then apply any necessary transforms to the view in `viewDidLayoutSubviews` (for the default appearance) and `viewWillTransitionToSize:withTransitionCoordinator` (for a change in orientation). – jamesk Dec 20 '15 at 02:23
  • Great answer. This also applies to UITextField's 'enabled' property. – Trevor Panhorst Apr 19 '16 at 22:56
  • 2
    Just a side note, the slowdown only occurs when debugging. If you launch the app manually there is no delay whatsoever. – Kevin May 25 '16 at 15:54
  • My two cents: it seems that UITextField is performing some background tasks, and that it has to wait for them to complete (something like waitUntilDone) in order to set `selectable` to `true`. Because setting `selectable` to `true` right after the UITextField has been loaded causes a freeze anyway. And even a second after that. But if I try to set it several seconds later, it is set instantly. So there's something like `waitUntilBackgroundTasksAreComplete` in the `selectable` setter. – FreeNickname Sep 08 '16 at 13:30
  • Where apple is looking? – Nike Kov Oct 21 '16 at 13:44
3

Tried something similar where a label and a text field were added as a subviews to a uiview subclass. The way I did was the following:

@interface CustomTextField : UIView

@property (weak, nonatomic) IBOutlet UITextField *valueField;

@end

So, we had a xib file on which we actually add the label and the text field. On the xib file, the file owner is "CustomTextField" and outlets are linked with the header file from there.

The constructor method looks like this:

- (id)initWithValue:(NSString *)value
{
    self = [super initWithFrame:CGRectZero];
    if (self) {
        NSArray *nibs = [[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class]) owner:self options:nil];
        UIView *view = nibs[0];
        self.valueField.frame = view.bounds;
        [self setFrame:view.bounds];
        [self addSubview:view];
        [self.valueField setText:value];
    }

    return self;
}

Works fine for me.

Anand
  • 864
  • 10
  • 31
  • Putting the `UITextView` in a xib and then loading the xib (as per [this answer](http://stackoverflow.com/a/34524346/3681880)) and unchecking `selectable` got rid of the long delay for my contrived example in my question (but getting my [original view](http://stackoverflow.com/questions/34134593/slow-load-time-for-custom-uiview-in-swift) to rotate correctly with the xib still hasn't worked). Anyway, this is one solution to my question here. – Suragch Dec 30 '15 at 10:25
1

downloaded your code from github, why don't you just write a subclass of UITextView instead of the UIView?

//
//  UIMongolTextView-A.swift
//  Mongol App Componants
//
//  Created by Allen Zhang on 12/13/15.
//  Copyright © 2015 MongolSuragch. All rights reserved.
//

import UIKit

class UIMongolTextView_A: UITextView {

    override func awakeFromNib() {
        super.awakeFromNib()
        self.setup()
    }

    func setup() {
        // 1-10: ᠨᠢᠭᠡ ᠬᠤᠶᠠᠷ ᠭᠤᠷᠪᠠ ᠳᠦᠷᠪᠡ ᠲᠠᠪᠤ ᠵᠢᠷᠭᠤᠭ᠎ᠠ ᠳᠤᠯᠤᠭ᠎ᠠ ᠨᠠᠢ᠌ᠮᠠ ᠶᠢᠰᠦ ᠠᠷᠪᠠ

        self.transform = translateRotateFlip()


    }

    /*
    // Only override drawRect: if you perform custom drawing.
    // An empty implementation adversely affects performance during animation.
    override func drawRect(rect: CGRect) {
        // Drawing code
    }
    */
    func translateRotateFlip() -> CGAffineTransform {

        var transform = CGAffineTransformIdentity

        // translate to new center
        transform = CGAffineTransformTranslate(transform, (self.bounds.width / 2)-(self.bounds.height / 2), (self.bounds.height / 2)-(self.bounds.width / 2))
        // rotate counterclockwise around center
        transform = CGAffineTransformRotate(transform, CGFloat(-M_PI_2))
        // flip vertically
        transform = CGAffineTransformScale(transform, -1, 1)

        return transform
    }
}
Allen
  • 6,505
  • 16
  • 19
  • Originally this is what I tried to do (since it is what I had done for Android). The problem is that rotating a `UITextView` messes up both the text and auto layout. See [this question](http://stackoverflow.com/questions/30907710/rotating-a-view-in-layoutsubviews) for more details. I appreciate you making the effort to download the project, though. – Suragch Dec 14 '15 at 01:14
1

I had a very similar performance issue with a UITextView but I think the root cause and solution were different for me.

I had a UITextView with static text on a storyboard. It was not editable or selectable. On a slow iPod Touch the ViewController would take 5 seconds to load.

Turns out that I had an emoji in the static text. When I removed the emoji the problem went away! I used instruments to dig into what was happening and about 25 levels deep in the trace it was getting into a bunch of ...font... methods. I was using the system font with no attributes so the first idea that came to mind was the emoji character in my text.

Chad Pavliska
  • 1,233
  • 12
  • 17