Limit Text to number of lines 3 - swift

I have to show some text in UILabel and append read more if the text goes beyond 3 number of lines. It works fine if I set number of lines = 3 and trim the text to 120 characters or so. But if the text contains newline character then this fails.
How to handle this.
func formatText() -> String {
var formatString = self.review_description
var maxLimit = 140
if self.review_link != nil {
maxLimit = 120
}
if formatString.count > maxLimit {
let substring = formatString.dropLast(formatString.count - maxLimit)
formatString = String(substring) + "... " + AppConstants.readMoreText
}
if self.review_link != nil {
formatString = formatString + " \(AppConstants.reviewSourceText)"
}
return formatString
}

try this
make your number of lines for label as 0. because new line won't increase your char count at all

Related

Algorithm on finding all 4 letter words in a string - where's the last item?

Posting a question here as ~find all ~4 letter words in a string ~algorithm site:stackoverflow.com google did not return any positive results.
The problem:
Write a function that takes a string of words and returns the number of 4 letter ones. The input argument 'sentence' is a string with words separated by spaces without punctuation.
So far I have a code like this:
func fourLetters(sentence: String) -> Int {
var targetNames = 0
var letterCount = 0
for letter in sentence {
if letter != " " {
letterCount += 1
} else {
print ("space found") // tech line
print (letterCount) // tech line
if letterCount == 4 {
targetNames += 1
letterCount = 0
}
letterCount = 0
}
}
print(targetNames) // tech line
return targetNames
}
The issue:
This algorithm now does not take into account the last part of the string giving the invalid number of 4 letter words. Consider we have the sentence: "Good Night Lil Peep" would return 1, although there are obviously two 4 teller words. What am I missing? Seems like the loop completely ignores the last word.
repl.it link for convenience and runs: https://repl.it/#DmitryAksyonov/4-lettered-names
Thank you for the help!
Regards
func findWords(ofLenght lenght: Int, in string: String) -> [Substring] {
let words = string.split{ $0.isWhitespace }
return words.filter { $0.count == lenght }
}
let input = "abcd word some string"
let result = findWords(ofLenght: 4, in: input)
print(result.count)
Output: 3
The problem is that the incrementation code for targetNames is encountered only if you find a space in the input string, which is not the case at the end of the string. You may be lucky if the last character of the string is a space.
So your code will fail even in the case where your sentence contains just one 4-letter word.
A possible solution is that you modify the condition of your first if statement by adding another condition to it which returns false if the loop has reached the end of the string, so that the else part of your code will run at that time and check if that last word is a 4-letter word.
Your algorithm doesn’t work because you don’t check for a 4-letter word at the end of the text.
You have to add another else clause and an index to check for end of text.
Of course there are more efficient ways to do that, it's just the answer to the question why the last item is not considered.
func fourLetters(sentence: String) -> Int {
var targetNames = 0
var letterCount = 0
var index = sentence.startIndex
for character in sentence {
sentence.formIndex(after: &index)
if character == " " { // check space
print ("space found") // tech line
print (letterCount) // tech line
if letterCount == 4 {
targetNames += 1
}
letterCount = 0
} else if index == sentence.endIndex { // check end of text
print ("end of text found") // tech line
letterCount += 1 // increment letterCount
if letterCount == 4 {
targetNames += 1
}
} else {
letterCount += 1
}
}
print(targetNames) // tech line
return targetNames
}
fourLetters(sentence: "Good Night Lil Peep") // 2

How to wrap a string at 72 charaters

I'm trying to split the text of a string into lines no longer than 72 characters (to break lines to the usual Usenet quoting line length). The division should be done by replacing a space with a new line (choosing the closest space so that every line is <= 72 characters). [edited]
The text is present in a string and could also contain emoji or other symbols.
I have tried different approaches but the fact that I can not separate a word but I must necessarily separate the text where there is a space has not allowed me to find a solution for now.
Does anyone know how this result can be obtained in Swift? Also with Regular expressions if needed. [edited]
In other languages you can index a string with an integer. Not so in Swift: you must interact with its character index, which can be a pain in the neck if you are not familiar with it.
Try this:
private func split(line: Substring, byCount n: Int, breakableCharacters: [Character]) -> String {
var line = String(line)
var lineStartIndex = line.startIndex
while line.distance(from: lineStartIndex, to: line.endIndex) > n {
let maxLineEndIndex = line.index(lineStartIndex, offsetBy: n)
if breakableCharacters.contains(line[maxLineEndIndex]) {
// If line terminates at a breakable character, replace that character with a newline
line.replaceSubrange(maxLineEndIndex...maxLineEndIndex, with: "\n")
lineStartIndex = line.index(after: maxLineEndIndex)
} else if let index = line[lineStartIndex..<maxLineEndIndex].lastIndex(where: { breakableCharacters.contains($0) }) {
// Otherwise, find a breakable character that is between lineStartIndex and maxLineEndIndex
line.replaceSubrange(index...index, with: "\n")
lineStartIndex = index
} else {
// Finally, forcible break a word
line.insert("\n", at: maxLineEndIndex)
lineStartIndex = maxLineEndIndex
}
}
return line
}
func split(string: String, byCount n: Int, breakableCharacters: [Character] = [" "]) -> String {
precondition(n > 0)
guard !string.isEmpty && string.count > n else { return string }
var string = string
var startIndex = string.startIndex
repeat {
// Break a string into lines.
var endIndex = string[string.index(after: startIndex)...].firstIndex(of: "\n") ?? string.endIndex
if string.distance(from: startIndex, to: endIndex) > n {
let wrappedLine = split(line: string[startIndex..<endIndex], byCount: n, breakableCharacters: breakableCharacters)
string.replaceSubrange(startIndex..<endIndex, with: wrappedLine)
endIndex = string.index(startIndex, offsetBy: wrappedLine.count)
}
startIndex = endIndex
} while startIndex < string.endIndex
return string
}
let str1 = "Iragvzvyn vzzntvav chooyvpngr fh Vafgntenz r pv fbab gnagvffvzv nygev unfugnt, qv zvabe fhpprffb, pur nttertnab vzzntvav pba y’vzznapnovyr zntyvrggn"
let str2 = split(string: str1, byCount: 72)
print(str2)
Edit: this turns out to be more complicated than I thought. The updated answer improves upon the original by processing the text line by line. You may ask why I devise my own algorithm to break lines instead of components(separatedBy: "\n"). The reason is to preserve blank lines. components(...) will collapse consecutive blank lines into one.

Swift 4 need help to make triangle using (*)

swift4 to make triangle tree using stars(*) and
its need to look a pine tree, I tried with the below code but it is not working as expected.
Its need to look like equilateral triangle.
var empty = "";
for loop1 in 1...5
{
empty = "";
for loop2 in 1...loop1
{
empty = empty + "*";
}
print (empty);
}
Now,
Expected
Not quite equilateral but as close as you're likely to get with character graphics. The main things are that you need an odd number of asterisks on each line for centering to work and you need to calculate an offset.
(And, even so, you need output in a monospaced font for this to look right.)
Edit: Some cleanup for readability (and incorporating the change from the first comment).
let treeHeight = 5
let treeWidth = treeHeight * 2 - 1
for lineNumber in 1...treeHeight {
// How many asterisks to print
let stars = 2 * lineNumber - 1
var line = ""
// Half the non-star space
let spaces = (treeWidth - stars) / 2
if spaces > 0 {
line = String(repeating: " ", count: spaces)
}
line += String(repeating: "*", count: stars)
print (line)
}
Ex:
*
***
*****
*******
*********
Code:
let numberOfRows = 5
for i in 0..<numberOfRows
{
var printst = ""
for a in 0..<(numberOfRows + 1)
{
let x = numberOfRows - i
if (a > x) {
printst = printst + "*"
} else {
printst = printst + " "
}
}
for _ in 0..<(i+1)
{
printst = printst + "*"
}
print(printst)
}
you can use this code to print star triangle
for i in 1...5{
for _ in 1...i{
print("*",terminator:"")
}
print(" ")
}

How to increment String in Swift

I need to save files in an alphabetical order.
Now my code is saving files in numeric order
1.png
2.png
3.png ...
The problem is when i read this files again I read this files as described here
So I was thinking of changing the code and to save the files not in a numeric order but in an alphabetical order as:
a.png b.png c.png ... z.png aa.png ab.png ...
But in Swift it's difficult to increment even Character type.
How can I start from:
var s: String = "a"
and increment s in that way?
You can keep it numeric, just use the right option when sorting:
let arr = ["1.png", "19.png", "2.png", "10.png"]
let result = arr.sort {
$0.compare($1, options: .NumericSearch) == .OrderedAscending
}
// result: ["1.png", "2.png", "10.png", "19.png"]
If you'd really like to make them alphabetical, try this code to increment the names:
/// Increments a single `UInt32` scalar value
func incrementScalarValue(_ scalarValue: UInt32) -> String {
return String(Character(UnicodeScalar(scalarValue + 1)))
}
/// Recursive function that increments a name
func incrementName(_ name: String) -> String {
var previousName = name
if let lastScalar = previousName.unicodeScalars.last {
let lastChar = previousName.remove(at: previousName.index(before: previousName.endIndex))
if lastChar == "z" {
let newName = incrementName(previousName) + "a"
return newName
} else {
let incrementedChar = incrementScalarValue(lastScalar.value)
return previousName + incrementedChar
}
} else {
return "a"
}
}
var fileNames = ["a.png"]
for _ in 1...77 {
// Strip off ".png" from the file name
let previousFileName = fileNames.last!.components(separatedBy: ".png")[0]
// Increment the name
let incremented = incrementName(previousFileName)
// Append it to the array with ".png" added again
fileNames.append(incremented + ".png")
}
print(fileNames)
// Prints `["a.png", "b.png", "c.png", "d.png", "e.png", "f.png", "g.png", "h.png", "i.png", "j.png", "k.png", "l.png", "m.png", "n.png", "o.png", "p.png", "q.png", "r.png", "s.png", "t.png", "u.png", "v.png", "w.png", "x.png", "y.png", "z.png", "aa.png", "ab.png", "ac.png", "ad.png", "ae.png", "af.png", "ag.png", "ah.png", "ai.png", "aj.png", "ak.png", "al.png", "am.png", "an.png", "ao.png", "ap.png", "aq.png", "ar.png", "as.png", "at.png", "au.png", "av.png", "aw.png", "ax.png", "ay.png", "az.png", "ba.png", "bb.png", "bc.png", "bd.png", "be.png", "bf.png", "bg.png", "bh.png", "bi.png", "bj.png", "bk.png", "bl.png", "bm.png", "bn.png", "bo.png", "bp.png", "bq.png", "br.png", "bs.png", "bt.png", "bu.png", "bv.png", "bw.png", "bx.png", "by.png", "bz.png"]`
You will eventually end up with
a.png
b.png
c.png
...
z.png
aa.png
ab.png
...
zz.png
aaa.png
aab.png
...
Paste this code in the playground and check result. n numbers supported means you can enter any high number such as 99999999999999 enjoy!
you can uncomment for loop code to check code is working fine or not
but don't forget to assign a lesser value to counter variable otherwise Xcode will freeze.
var fileName:String = ""
var counter = 0.0
var alphabets = ["a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z"]
let totalAlphaBets = Double(alphabets.count)
let numFiles = 9999
func getCharacter(counter c:Double) -> String {
var chars:String
var divisionResult = Int(c / totalAlphaBets)
let modResult = Int(c.truncatingRemainder(dividingBy: totalAlphaBets))
chars = getCharFromArr(index: modResult)
if(divisionResult != 0){
divisionResult -= 1
if(divisionResult > alphabets.count-1){
chars = getCharacter(counter: Double(divisionResult)) + chars
}else{
chars = getCharFromArr(index: divisionResult) + chars
}
}
return chars
}
func getCharFromArr(index i:Int) -> String {
if(i < alphabets.count){
return alphabets[i]
}else{
print("wrong index")
return ""
}
}
for _ in 0...numFiles {
fileName = getCharacter(counter: counter)+".png"
print(fileName)
counter += 1
}
fileName = getCharacter(counter: Double(numFiles))+".png"
print(fileName)

Read lines from big text file in Swift until new line is empty: the Swift way

I have the following text file structure (the text file is pretty big, around 100,000 lines):
A|a1|111|111|111
B|111|111|111|111
A|a2|222|222|222
B|222|222|222|222
B|222|222|222|222
A|a3|333|333|333
B|333|333|333|333
...
I need to extract a piece of text related to a given key. For example, if my key is A|a2, I need to save the following as a string:
A|a2|222|222|222
B|222|222|222|222
B|222|222|222|222
For my C++ and Objective C projects, I used the C++ getline function as follows:
std::ifstream ifs(dataPathStr.c_str());
NSString* searchKey = #"A|a2";
std::string search_string ([searchKey cStringUsingEncoding:NSUTF8StringEncoding]);
// read and discard lines from the stream till we get to a line starting with the search_string
std::string line;
while( getline( ifs, line ) && line.find(search_string) != 0 );
// check if we have found such a line, if not report an error
if( line.find(search_string) != 0 )
{
data = DATA_DEFAULT ;
}
else{
// we need to form a string that would include the whole set of data based on the selection
dataStr = line + '\n' ; // result initially contains the first line
// now keep reading line by line till we get an empty line or eof
while(getline( ifs, line ) && !line.empty() )
{
dataStr += line + '\n'; // append this line to the result
}
data = [NSString stringWithUTF8String:navDataStr.c_str()];
}
As I am doing a project in Swift, I am trying to get rid of getline and replace it with something "Cocoaish". But I cannot find a good Swift solution to address the above problem. If you have an idea, I would really appreciate it. Thanks!
Using the StreamReader class from Read a file/URL line-by-line in Swift, you could do that it Swift like this:
let searchKey = "A|a2"
let bundle = NSBundle.mainBundle()
let pathNav = bundle.pathForResource("data_apt", ofType: "txt")
if let aStreamReader = StreamReader(path: pathNav!) {
var dataStr = ""
while let line = aStreamReader.nextLine() {
if line.rangeOfString(searchKey, options: nil, range: nil, locale: nil) != nil {
dataStr = line + "\n"
break
}
}
if dataStr == "" {
dataStr = "DATA_DEFAULT"
} else {
while let line = aStreamReader.nextLine() {
if countElements(line) == 0 {
break
}
dataStr += line + "\n"
}
}
aStreamReader.close()
println(dataStr)
} else {
println("cannot open file")
}