0

I got a long string, let's call it Story , I fetch this Story from a Database so i don't know how long it is.

I want to display this story in view , But what if the Story is to long that doesn't fit in one view ?

I don't want to adjusts the font size Because it may be a very long story and make the font size smaller is not a good solution.

So i want to separate the Story to more than one view , By Passing the Story and get separated Story as array of String every item in the array can fit in one view .

This is the code , Maybe it give you a hint what i'm trying to do :

extension String {

    /// - returns: The height that will fit Self
    func height(withConstrainedWidth width: CGFloat, font: UIFont) -> CGFloat {
        let constraintRect = CGSize(width: width, height: .greatestFiniteMagnitude)
        let boundingBox = self.boundingRect(with: constraintRect, options: .usesLineFragmentOrigin, attributes: [.font: font], context: nil)
        return ceil(boundingBox.height)
    }

    #warning("it's so slow that i can't handle it in main thread , ( 21 second for 15 items in array )")
    /// complition contains the separated string as array of string
    func splitToFitSize(_ size : CGSize = UIScreen.main.bounds.size ,font : UIFont = UIFont.systemFont(ofSize: 17) , complition : @escaping (([String]) -> Void) )  {
        DispatchQueue.global(qos: .background).async {

            // contents contains all the words as Array of String
            var contents = self.components(separatedBy: .whitespaces)

            var values : [String] = []
            for content in contents {
                // if one word can't fit the size -> remove it , which it's not good, but i don't know what to do with it
                guard content.isContentFit(size: size , font : font) else {contents.removeFirst(); continue;}
                if values.count > 0 {
                    for (i , value) in values.enumerated() {
                        var newValue = value
                        newValue += " \(content)"
                        if newValue.isContentFit(size: size, font: font) {
                            values[i] = newValue
                            contents.removeFirst()
                            break;
                        }else if i == values.count - 1 {
                            values.append(content)
                            contents.removeFirst()
                            break;
                        }
                    }
                }else {
                    values.append(content)
                    contents.removeFirst()
                }
            }
            complition(values)
        }
    }

    /// - returns: if Self can fit the passing size
    private func isContentFit(size : CGSize, font : UIFont) -> Bool{
        return self.height(withConstrainedWidth: size.width, font: font) < size.height
    }
}

This code is working , But it take so long , if i want to split a Story to 15 views it take 20 or more second.

I'm not good in algorithms so i need a batter or a hint to make it execute quicker.

Just Hint in Any programming language, i be very thankful .

11
  • 2
    Yeah, so I am not sure what your application is intended for but what I meant is maybe you can instantiate a UIScrollViewController and add its view as subview in your main controller's view. String manipulation usually takes a long time Commented Apr 30, 2019 at 19:20
  • 1
    Maybe this question would be more fit for Code Review ? Commented Apr 30, 2019 at 19:27
  • 1
    Maybe this might help? Commented Apr 30, 2019 at 19:50
  • 1
    Using removeFirst(_:) is an O(n) operation, it is the main bottleneck in this code. Commented Apr 30, 2019 at 19:51
  • 1
    cross posted to CodeReview Commented May 3, 2019 at 14:48

1 Answer 1

1

I have worked with this some times, for example for implementing an e-book reader, where you want the pages to flow to the right.

The code you posted is essentially correct, but it is slow because it has to rerender everything word-for-word as you measure it's size (assuming that your function isContentFit() is taking up most of your time). If you want to optimize this, you have to make a rough guess at the size of each letter and get an estimate on how many of your words can fit on one page before beginning to render and measure. You could also delay rendering/measuring the forthcoming pages until just before they need to display.

Another problem could also be the way you split up the string into words and then concatenate them back one by one. This can also be slow and time consuming if the string is large. Here it would also benefit to just search through the original string and counting letters and spaces, and then use string slicing when you know where to cut the string into pages.

Another solution I have participated in using successfully multiple times, is using a webview and styling your text so it will display in columns, for example by using styling like in this example: https://www.w3schools.com/cssref/tryit.asp?filename=trycss3_column-width The web rendering engine does something like your code does, but blazingly fast.

On iOS, you should use the css property -webkit-column-width (since webviews on iOS is essentially rendered like in Safari) and set -webkit-column-width to the width of your webview, and then render the entire text into the webview. The result will then be that you see one 'page' at a time, and can scroll to the right to see the next page.

If you want to use a page controller or some other scrolling control on top of this, you have to inject some css/javascript magic, which is not easy I must admit, but it can be done.

Here is an example html string to display in the webview. Just insert your entire story string into the div:

<html>
<head>
<style> 
.multicolumn {
    -webkit-column-width: 500px; /* set this to the width of your webview */
    height: 300px; /* set this to the height of your webview */
    overflow-x: scroll;
}
</style>
</head>
<body>
<div class="multicolumn">

Insert long long text here...

</div>

</body>
</html>
Sign up to request clarification or add additional context in comments.

2 Comments

i don't want to inject any html, css or javascript on my project and I don't want to add story in webview but you give me a great idea of how to do it , i keep you informed
So my stupid idea not working but github.com/nishanthooda/FitMyLabel what i need thanks to @Sweeper and github.com/nishanthooda and thank you.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.