Search code examples
iosxcodetextuiscrollviewscroll-paging

How To Separate Strings For UIScrollView/UITextView based on the size of the frame


I'm currently trying to read in a RSS feed and separate the long paragraphed text into a UIScrollView with paging enabled. I need to separate the text into different pages aka fit what will fit on each page and separate the string as such. I'm not sure if there is a standard what of doing this and I assume that this is how most RSS reading apps separate their information on multiple pages. Does anyone know how to tackle this? I did not want to look letter by letter until the text didn't fit and continue.

edit:

This is a good start, but the example code pretty much runs into the problems I was trying to avoid and don't know how to get around. This range calculates incorrectly for the UITextView. I change the font and such as seen below. Everything is attempting to being calculated within - (NSRange)visibleRangeOfTextView:(UITextView *)textView. This method is called by -(void)adjustTextDisplay which is called by an external class after setting the text for the UITextView. I have no idea why setting the content size to the frame size of the screen does not restrict the view (as shown below) nor do I know why this method is returning the full string length as the range.


Solution

  • As of iOS 7, there's a much more elegant solution to this using TextKit that I've included in sample code below. The idea is to let TextKit's layout manager handle separating the glyphs and lay everything out that way properly. This prevents cutting off words mid way and a ton of flexibility:

    class BookView: UIScrollView {
    
        var bookMarkup: NSAttributedString!
        private let layoutManager = NSLayoutManager()
    
        override func layoutSubviews() {
            super.layoutSubviews()
    
            if layoutManager.textContainers.count == 0 {
                buildFrames()
            }
        }
    
        func buildFrames() {
            let textStorage = NSTextStorage(attributedString: bookMarkup)
    
            textStorage.addLayoutManager(layoutManager)
    
            var range = NSMakeRange(0, 0)
            var containerIndex = 0
    
            while NSMaxRange(range) < layoutManager.numberOfGlyphs {
                let textViewRect = frameForViewAtIndex(containerIndex)
                let containerSize = CGSizeMake(CGRectGetWidth(textViewRect), CGRectGetHeight(textViewRect) - 16) //UITextView adds an 8 margin above and below the container so we take that into consideration here with the 16. heightTracksTextView causes a performance hit when adding multiple containers... so we don't do that instead
                let textContainer = NSTextContainer(size: containerSize)
                layoutManager.addTextContainer(textContainer)
    
                let textView = UITextView(frame: textViewRect, textContainer: textContainer)
    
                addSubview(textView)
    
                containerIndex++
    
                range = layoutManager.glyphRangeForTextContainer(textContainer)
            }
    
            contentSize = CGSize(width: CGRectGetWidth(bounds) / 2 * CGFloat(containerIndex), height: CGRectGetHeight(bounds))
    
            pagingEnabled = true
        }
    
        private func frameForViewAtIndex(index: Int) -> CGRect {
    
            var textViewRect = CGRect(origin: CGPointZero, size: CGSize(width: CGRectGetWidth(bounds)/2, height: CGRectGetHeight(bounds)))
            textViewRect = CGRectInset(textViewRect, 10, 20)
            textViewRect = CGRectOffset(textViewRect, CGRectGetWidth(bounds) / 2 * CGFloat(index), 0)
            return textViewRect
        }
    }