Getting string from label visible at runtime - ios

I want to get the string which is visible at my label and having line break mode .byClipping .
My real string is :-
"This way your API interface will be nice and pretty, you’re not exposing the internals of your class to the world. If you have constants that are used in lots of classes all over the app then it makes sense to have a separate .h and .m file just for defining these constants."
but at run time i am getting :-
"This way your API interface will be nice and pretty, you’re not exposing the internals of your class to the world. If you have constants"
i want to get the below string in a variable at runtime.

I am not sure if this is natively doable. The closest idea I have is to test the string size and removing/adding characters. Check if the following works for you:
func findTextActuallyVisibleInLabel(_ label: UILabel) -> String? {
guard let originalText = label.text else { return nil }
var text = originalText
let labelSize = label.bounds
guard labelSize.height > 0 else { return text }
let bounds: CGRect = CGRect(x: 0.0, y: 0.0, width: labelSize.width, height: CGFloat.infinity)
while !text.isEmpty && label.textRect(forBounds: bounds, limitedToNumberOfLines: label.numberOfLines).height > labelSize.height {
text.removeLast()
label.text = text
}
label.text = originalText
return text
}
This seems to do its job in my case more or less. If I use wrapping that ends text with ... the result is incorrect.
Also some optimization may be nice; currently I just use the full string and start subtracting character by character. Maybe a bisection would be nicer here.
I do still use original label so it should take all possible parameters into account like wrapping, font size, lines... I modify the actual text on the label which is then reset to original.
Possibly view animations should be disabled while this operation is in progress.

Related

SwiftUI:! [Array] the compiler is unable to type-check this expression in reasonable time

Can you help me solve this problem?
The problem is in the second button with the string array inside the Text Code, if there is an string array inside the text code mark the typical error: "the compiler is unable to type-check this expression in reasonable time", but if I change the string array to a normal string, there is no error.
The second button just appears if the sti array has 2 or more elements inside it with the second element.
Group {
let telephone = "tel://"
Button(action:{
let formattedString = telephone + CardPn[0]
guard let url = URL(string: formattedString) else { return }
UIApplication.shared.open(url)
}){
Text(CardPn[0])
.fontWeight(.bold)
.underline()
}
if CardPn.count >= 2{
Button(action:{
let formattedString = telephone + CardPn[1]
guard let url = URL(string: formattedString) else { return }
UIApplication.shared.open(url)
}){
Text(CardPn[1])
.fontWeight(.bold)
.underline()
}
}
}
General advice to fix the-compiler-is-unable-to-type-check-this-expression-in-reasonable-time errors:
Use explicit types for your variables:
let telephone = "tel://" // Not Optimal
let telephone: String = "tel://" // This is Better because of the explicit `: String`
Use smaller declarations instead of a one big thing that contains all your logic:
var body: some View {
// Not optimal button
Button(action: {
// do something
.
.
.
}, label: {
Text("something")
.
.
.
})
// Better example of a button
Button(action: actionOfTheButton, label: buttonLabel)
}
// Declare the action somewhere else
func actionOfTheButton() {
// do something
.
.
.
}
// Label of the button is declared here
var buttonLabel: some View {
Text("something")
.
.
.
}
Avoid big calculations in one variable:
// We want to calculate the finalArea
// some variables to showcase
let someWidth: CGFloat = 200
let someHeight: CGFloat = 450
let ratio: CGFloat = 0.75
let maxSize: CGFloat = 80000
let minSize: CGFloat = 100
// not optimal because there are lots calculations going on in one line
// to calculate the finalArea
let finalArea = max(min((someWidth * someHeight) * ratio, minSize), maxSize)
// this approach is better, both for compiler and for code-readability,
// finalArea is calculated in 3 steps instead of 1:
let initialArea = someWidth * someHeight
let areaWithRatio = initialArea * ratio
let finalArea = max(min(areaWithRatio, minSize), maxSize)
Make sure you don't have something too big. e.g. an array containing 5000 lines of Elements will likely make the compiler complain.
The last possibility i remember, is that you have a syntax error or other errors on your side.
In this case you should just try to eye-ball the problem and fix it.
You don't need to go all-in in declaring explicit types, as thats one of the nice features of the Swift language and you don't want to be throwing that away, but as a matter of fact, that always helps the compiler.
About the second tip, it is generally advised to cut your app to smaller components instead of big components. That also helps your code to be better readable and more manageable. The bigger your project, the more you thank yourself for these smaller components.

Parse unicode character from server, iOS Swift

I use a paid set of linear icon's from this website.
It's great! Especially in iOS I put the .ttf file in my projects bundle, load the font and use it in labels and buttons. I even wrote an article about how I do this.
My problem comes when I want to dynamically change a label based on some server value. My initial instinct was to save the unicode value as text up on the server. I simply save the the value such as ed02 and when I pull it down into my App I add it to, let's say, a label like this.
let label = UILabel(frame: CGRect(x: 0, y: 0, width: 100, height: 20))
label.font = IconUltimate().icoUltimateFont(18)
let valueFromServer = "ed02"
label.text = "\u{\(valueFromServer)}"
The problem is that the line:
label.text = "\u{\(valueFromServer)}"
is invalid. What am I doing wrong? Is there a way to inject a unicode value from the server into my UI? My solution right now is to map the unicode value from the server using a switch statement like this:
public func unicodeMapper(rawUnicode: String) -> String {
switch rawUnicode {
case "ecf5":
let thumbs_up = "\u{ecf5}"
return thumbs_up
default:
return ""
}
}
And call it like this:
let valueFromServer = "ed02"
label.text = unicodeMapper(rawUnicode: valueFromServer)
Anyone have any suggestions so I don't have to use a switch statement and I can just inject the value from the server?
Thanks
Like this:
let code = "ecf5" // or whatever you got from the server
let codeint = UInt32(code, radix: 16)!
let c = UnicodeScalar(codeint)!
label.text = String(c)

getting consistent sizing for glyph backgrounds

Hey folks – I'm new to TextKit and trying to draw backgrounds & borders around specific attributes. I've gotten fairly close, but haven't yet found methods that don't generate very inconsistent sizing, which tends to look bad. Here's my first crack at it:
class MyLayout: NSLayoutManager {
override func drawBackground(forGlyphRange glyphsToShow: NSRange, at origin: CGPoint) {
super.drawBackground(forGlyphRange: glyphsToShow, at: origin)
guard let storage = textStorage else {
return
}
guard let context = UIGraphicsGetCurrentContext() else {
return
}
var codeBlockRect: CGRect? = nil
enumerateLineFragments(forGlyphRange: glyphsToShow) { (rect, usedRect, container, subRange, stop) in
var effectiveRange = NSRange()
let attributes = storage.attributes(at: subRange.location, effectiveRange: &effectiveRange)
storage.enumerateAttribute(.inlineCodeBlock, in: subRange) { (value, attributeRange, stop) in
guard value != nil else {
return
}
var background = self.boundingRect(forGlyphRange: attributeRange, in: container)
background.origin.x += origin.x
background.origin.y += origin.y
context.setFillColor(UIColor.lightGrey.cgColor)
context.setStrokeColor(UIColor.mediumGrey.cgColor)
context.stroke(background)
context.fill(background)
}
}
}
}
That produces these results:
Single line of text:
Multiple lines of text:
As you can see, there's about 3 pixels of difference between the sizes there. I imagine it's because boundingRect, as the documentation says:
Returns the smallest bounding rect which completely encloses the glyphs in the given glyphRange
But I haven't found a method that gives me a number closer to what I'm looking for. My ideal scenario is that every rectangle will have the exact same height.
Let me know if any more information is needed.
Update
It crossed my mind that this could be related to the proprietary font we're using, so I changed everything to use UIFont.systemFont, which didn't make any difference.
I found a workaround here – instead of defining my inlineCodeBlock as a background, i defined it as a custom underline style.
Doing that let me override drawUnderline(forGlyphRange:underlineType:baselineOffset:lineFragmentRect:lineFragmentGlyphRange:containerOrigin:).
Once I had that, I was able to use baselineOffset to get a consistent positioning.

Swift: Display (LaTeX) math expressions inline

I would like to display math terms inside a text, in particular in an inline mode, i.e. inside a sentence.
Using LaTeX, this would for example look like:
"Given a right triangle having catheti of length \(a\) resp. \(b\) and a hypotenuse of length \(c\), we have
\[a^2 + b^2 = c^2.\]
This fact is known as the Pythagorean theorem."
Does anybody know how this can be achieved in Swift?
(I know that this example may be achieved in Swift without LaTeX-like tools. However, the expressions in my mind are in fact more complex than in this example, I do need the power of LaTeX.)
The optimal way would be a UITextView-like class which recognizes the math delimiters \(,\) resp. \[,\], recognizes LaTeX code inside these delimiters, and formats the text accordingly.
In the Khan Academy app, this problem seems to be solved as the screenshots in the Apple App Store/Google Play Store show inline (LaTeX) math.
I’ve found the package iosMath which provides a UILabel-like class MTMathUILabel. As this class can display solely formulas, this seems to be not good enough for my purpose, except if there was a method which takes a LaTeX source text such as in the example above, formats expressions such as \(a\) into tiny MTMathUILabels and sets these labels between the other text components. As I am new to Swift, I do not know whether and how this can be achieved. Moreover, this seems to be very difficult from a typographical point of view as there will surely occur difficulties with line breaks. And there might occur performance issues if there are a large number of such labels on the screen at the same time?
It is possible to achieve what I want using a WKWebView and MathJax or KaTeX, which is also a hack, of course. This leads to other difficulties, e.g. if one wants to set several of these WKWebViews on a screen, e.g. inside UITableViewCells.
Using iosMath, my solution on how to get a UILabel to have inline LaTeX is to include LATEX and ENDLATEX markers with no space. I replaced all ranges with an image of the MTMathUILabel, going from last range to first range so the positions don't get screwed up (This solution allows for multiple markers). The image returned from my function is flipped so i used .downMirrored orientation, and i sized it to fit my text, so you might need to fix the numbers a little for the flip scale of 2.5 and the y value for the attachment.bounds.
import UIKit
import iosMath
let question = UILabel()
let currentQuestion = "Given a right triangle having catheti of length LATEX(a)ENDLATEX resp. LATEX(b)ENDLATEX and a hypotenuse of length LATEX(c)ENDLATEX, we have LATEX[a^2 + b^2 = c^2]ENDLATEX. This fact is known as the Pythagorean theorem."
question.text = currentQuestion
if (question.text?.contains("LATEX"))! {
let tempString = question.text!
let tempMutableString = NSMutableAttributedString(string: tempString)
let pattern = NSRegularExpression.escapedPattern(for: "LATEX")
let regex = try? NSRegularExpression(pattern: pattern, options: [])
if let matches = regex?.matches(in: tempString, options: [], range: NSRange(location: 0, length: tempString.count)) {
var i = 0
while i < matches.count {
let range1 = matches.reversed()[i+1].range
let range2 = matches.reversed()[i].range
let finalDistance = range2.location - range1.location + 5
let finalRange = NSRange(location: range1.location, length: finalDistance)
let startIndex = String.Index(utf16Offset: range1.location + 5, in: tempString)
let endIndex = String.Index(utf16Offset: range2.location - 3, in: tempString)
let substring = String(tempString[startIndex..<endIndex])
var image = UIImage()
image = imageWithLabel(string: substring)
let flip = UIImage(cgImage: image.cgImage!, scale: 2.5, orientation: .downMirrored)
let attachment = NSTextAttachment()
attachment.image = flip
attachment.bounds = CGRect(x: 0, y: -flip.size.height/2 + 10, width: flip.size.width, height: flip.size.height)
let replacement = NSAttributedString(attachment: attachment)
tempMutableString.replaceCharacters(in: finalRange, with: replacement)
question.attributedText = tempMutableString
i += 2
}
}
}
func imageWithLabel(string: String) -> UIImage {
let label = MTMathUILabel()
label.latex = string
label.sizeToFit()
UIGraphicsBeginImageContextWithOptions(label.bounds.size, false, 0)
defer { UIGraphicsEndImageContext() }
label.layer.render(in: UIGraphicsGetCurrentContext()!)
return UIGraphicsGetImageFromCurrentImageContext() ?? UIImage()
}

How can I prevent orphans in a label in swift?

I have a label that can have one or two lines. If it has two lines, I want the second line to have at least two (or maybe three) words, never just one. Any ideas about how I can accomplish that using swift?
Thanks in advance!
Daniel
Edit: I edited out my silly first thoughts that didn't really help.
Ok, after looking around a lot I came up with what I think is the best solution.
I wrote this function:
func numberOfLinesInLabelForText(text: String) -> Int {
let attributes = [NSFontAttributeName : UIFont.preferredFontForTextStyle(UIFontTextStyleHeadline)]
let screenSize: CGRect = UIScreen.mainScreen().bounds
let labelSize = text!.boundingRectWithSize(CGSizeMake((screenSize.width - 30), CGFloat.max), options: NSStringDrawingOptions.UsesLineFragmentOrigin, attributes: attributes, context: nil)
let lines = floor(CGFloat(labelSize.height) / bookTitleLabel.font.lineHeight)
return Int(lines)
}
You put in the string that will be displayed in the label and it gives you how many lines the label will have. I'm using dynamic type and the Headline style for this particular label, hence the preferredFontForTextStyle(UIFontTextStyleHeadline) part, but you can change that to the font and size your label uses.
Then I use (screenSize.width - 30) for my label's width because it's width is not fixed, so I'm using the screen size minus leading and trailing. This is probably not the most elegant solution, I'm open to better suggestions.
The rest is pretty straightforward.
After I have the number of lines I can do this:
func splittedString(text: String) -> String {
if numberOfLinesInLabel(text) == 2 {
var chars = Array(text.characters)
var i = chars.count / 2
var x = chars.count / 2
while chars[i] != " " && chars[x] != " " {
i--
x++
}
if chars[i] == " " {
chars.insert("\n", atIndex: i+1)
} else {
chars.insert("\n", atIndex: x+1)
}
return String(chars)
}
}
Instead of just avoiding orphans I decided to split the string in two at the breaking point nearest to its half, so that's what this last function does, but it wouldn't be hard to tweak it to suit your needs.
And there you have it!

Resources