Center 3 Uneven Labels - Interface Builder - ios

Attached is an image of what I'm trying to center and the attempts I have made using XCode 11.2.1
How do I center the 3 labels of different sizes (the data for the number will be dynamic also) horizontally in the view?
Things I have tried to do:
I tried putting a horizontal constraint on the LateBEDView (which contains the 3 labels), but when you view it on the Simulator or Actual Device it puts all the text to the right of the center. I have tried using spacers (empty views) on both sides but can't figure out what the settings should be? Any help is greatly appreciated!

Here's how I would do it. I'd make this one label, which after all is easily centered. Okay, I'll assume you know how to do that.
Then I'd use an attributed string to create the different parts of the string, including the "subscripting". So, first I'd extend the attributed string keys to include the three parts:
extension NSAttributedString.Key {
static let part1 = NSAttributedString.Key(rawValue: "part1")
static let part2 = NSAttributedString.Key(rawValue: "part2")
static let part3 = NSAttributedString.Key(rawValue: "part3")
}
Then I'd set the label's attributed text:
let mas = NSMutableAttributedString()
mas.append(NSAttributedString(string: "Late BED = ", attributes: [
.font:UIFont.systemFont(ofSize: 15),
.foregroundColor:UIColor.black,
.part1:"part1"
]))
mas.append(NSAttributedString(string: "20.0", attributes: [
.font:UIFont.systemFont(ofSize: 15),
.foregroundColor:UIColor.black,
.part2:"part2"
]))
mas.append(NSAttributedString(string: " Gy(3.0)", attributes: [
.font:UIFont.systemFont(ofSize: 9),
.foregroundColor:UIColor.black,
.baselineOffset:-10,
.part3:"part3"
]))
self.label.attributedText = mas
The result looks like what you've got, and of course you can tweak it as needed:
Okay, and here's the really clever part. Because I demarcated the three parts of the attributed string, I can find and change the text of any of them, at will. As you say, the data for each number needs to be able to change. That's why you used three labels! But now I'm showing how to do that with one label.
For example, let's say I want to change "20.0" to "30.4". That is your second label, and my .part2. Here's how you do it:
let s = self.label.attributedText
// skip past part 1 and find part 2
var r = NSRange()
let mas = s?.mutableCopy() as! NSMutableAttributedString
let _ = mas.attribute(.part2, at: 0, longestEffectiveRange: &r,
in: NSRange(location: 0, length: 100))
// find range of part 2
let _ = mas.attribute(.part2, at: r.length, longestEffectiveRange: &r,
in: NSRange(location: 0, length: 100))
mas.replaceCharacters(in: r, with: "30.4")
self.label.attributedText = mas

Related

How to apply Atrributed String different spacing between lines in swift

I want to apply spacing between first two lines in attributed string and third line should look like paragraph.
Expected output :
Expected output screenshot
Current Implemenation:
Current implementaion screenshot
Here is the code tried by me.
let myString = "Your account phone numbers are listed here.\nTo change or delete a phone number, tap on it.\nTo add a phone number, go to the top right-hand corner of your screen and tap on “Add”.";
let font = UIFont.systemFont(ofSize: 14)
let attributedString = NSMutableAttributedString(string: myString, attributes: [.font: font])
self.displayLabel.attributedText = attributedString
I created label and setting number of lines 0 so it will display multiline text.
In the label need to show space in the first two lines as shown in expected output screenshot.
How to apply spacing only to first two lines and third line should display as shown in expected output screenshot?
You seem to want to set the spacing between paragraphs. This is controlled by NSParagraphStyle.paragraphSpacing. Just set the .paragraphStyle attribute of the attributed string to an NSParagraphStyle:
let paraStyle = NSMutableParagraphStyle()
paraStyle.paragraphSpacing = 10 // or some other number
let attributedString = NSMutableAttributedString(string: myString,
attributes: [
.font: font,
.paragraphStyle: paraStyle
])

How can I access a character's alternate font glyph to use in UILabel or UIButton text

I want to use alternate font glyphs in the text of a UILabel. For example, SF Mono has a "0" with no slash. I can see the alternate glyphs in Photoshop, if I use one as a text object and copy/paste it as a character to Xcode, it doesn't paste as the alternate, it pastes as the usual slashed version.
You aren't going to achieve this using mere copy and paste in Xcode. You're going to have to drop down to the level of core text and do it in code.
In this example, I'm using SFMono to display two zeroes, but one zero has the slash and the other doesn't:
let f = UIFont.monospacedSystemFont(ofSize: 16, weight: .regular)
let desc = f.fontDescriptor
let mas = NSMutableAttributedString(string: "00", attributes: [.font:f])
let d = [
UIFontDescriptor.FeatureKey.featureIdentifier: kStylisticAlternativesType,
UIFontDescriptor.FeatureKey.typeIdentifier: kStylisticAltThreeOnSelector
]
let desc2 = desc.addingAttributes([.featureSettings:[d]])
let f2 = UIFont(descriptor: desc2, size: 0)
mas.addAttributes([.font:f2], range: NSRange(location: 1, length: 1))
self.label.attributedText = mas
And here's the resulting label:

Unable to render an NSAttributedString as a 2 column tabbed bullet list in a PDF

I am constructing a large string that is output into a PDF file, but right now, I'd like to have a 2 column, bulleted list in my document. However, I have yet to figure out the correct settings that will allow me to get the desired tabbing effect.
Currently, I am testing the following code:
let mutableString = NSMutableAttributedString()
let words = ["this", "is", "really", "getting", "old"]
let paragraphStyle = NSMutableParagraphStyle()
var tabStops = [NSTextTab]()
let tabInterval: CGFloat = 250.0
for index in 0..<12 {
tabStops.append(NSTextTab(textAlignment: .left,
location: tabInterval * CGFloat(index),
options: [:]))
}
paragraphStyle.tabStops = tabStops
for index in 0..<words.count {
if index != 0 && index % 2 == 0 {
mutableString.append(NSAttributedString(string: "\n"))
}
if index % 2 == 1 {
let attributedText = NSAttributedString(string: "\t", attributes: [NSAttributedString.Key.paragraphStyle: paragraphStyle])
mutableString.append(attributedText)
}
let word = words[index]
let attributedString = NSMutableAttributedString(string: "\u{2022} \(word)",
attributes: [:])
mutableString.append(attributedString)
}
When I feed this into my PDF generator, it produces the following result:
Ultimately, I want "is" and "getting" to be aligned with the middle of the document, so that I can accommodate much larger words.
It turns out that I was in the ballpark, but definitely not close.
The following provides the desired split column effect:
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.tabStops = [
// 274 would be the midpoint of my document
NSTextTab(textAlignment: .left, location: 274, options: [:])
]
let string = "\u{2022} This\t\u{2022} is\n\u{2022} getting\t\u{2022} really\n\u{2022} old"
let attributedString = NSAttributedString(
string: string,
attributes: [NSAttributedString.Key.paragraphStyle: paragraphStyle]
)
For bonus points, should you want to have multiple columns in your document, the following will accomplish this (pardon my crude formatting):
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.tabStops = [
NSTextTab(textAlignment: .left, location: 100, options: [:]),
NSTextTab(textAlignment: .left, location: 300, options: [:])
]
let string = "\u{2022} This\t\u{2022} is\t\u{2022} getting\n\u{2022} really\t\u{2022} old"
let attributedString = NSAttributedString(
string: string,
attributes: [NSAttributedString.Key.paragraphStyle: paragraphStyle]
)
And will look like this:
What is going on here?
So, what I learned here is that the tabStops tells iOS what location within the line to place the tab:
The first tab will go to position 100
The second tab will go to position 300
A third tab will wrap around the document and go to position 100 as well
Regarding tabbing, if you assign a tab with location 0 in the first index, then tabbing to a newline will get it aligned with the left edge.
As to what fixed the issue for me. I was relying on an approach where each component of the string was added as it was encountered. However, this string would fail to format properly. Instead, by merging everything into a single string and applying the attributes seen in my working code, I was able to get it to align properly.
I also tested using the individual components as seen in my question, but with the paragraph style attributes applied as well, and that resulted in a working solution as well.
Based on this, it appears that my mistake was to mix strings that had, and did not have, the desired tabbing behavior.

How to split NSAttributedString across equal bounds views with correct word wrap

I have been grappling with this since a while. There are APIs that give us bounds size for given attributes of NSAttributedString.
But there is no direct way to get string range that would fit within given bounds.
My requirement is to fit very long string across a few paged views (PDF is not an option, neither is scrolling). Hence I have to figure out string size for each view (same bounds).
Upon research I found that CTFramesetterSuggestFrameSizeWithConstraints and its friends in Core Text maybe of help. I tried the approach described here, but the resulting ranges have one ugly problem:
It ignores word breaks (a different problem unrelated to Core Text, but I would really like to see if there was some solution to that as well).
Basically I want paging of text across number of UITextView objects, but not getting the right attributed string splits.
NOTE:
My NSAttributedString attributes are as follows:
let attributes: [NSAttributedString.Key : Any] = [.foregroundColor : textColor, .font : font, .paragraphStyle : titleParagraphStyle]
(titleParagraphStyle has lineBreakMode set to byWordWrapping)
extension UITextView
{
func getStringSplits (fullString: String, attributes: [NSAttributedString.Key:Any]) -> [String]
{
let attributeString = NSAttributedString(string: fullString, attributes: attributes)
let frameSetterRef = CTFramesetterCreateWithAttributedString(attributeString as CFAttributedString)
var initFitRange:CFRange = CFRangeMake(0, 0)
var finalRange:CFRange = CFRangeMake(0, fullString.count)
var ranges: [Int] = []
repeat
{
CTFramesetterSuggestFrameSizeWithConstraints(frameSetterRef, initFitRange, attributes as CFDictionary, CGSize(width: bounds.size.width, height: bounds.size.height), &finalRange)
initFitRange.location += finalRange.length
ranges.append(finalRange.length)
}
while (finalRange.location < attributeString.string.count)
var stringSplits: [String] = []
var startIndex: String.Index = fullString.startIndex
for n in ranges
{
let endIndex = fullString.index(startIndex, offsetBy: n, limitedBy: fullString.endIndex) ?? fullString.endIndex
let theSubString = fullString[startIndex..<endIndex]
stringSplits.append(String(theSubString))
startIndex = endIndex
}
return stringSplits
}
}
My requirement is to fit very long string across a few paged views (PDF is not an option, neither is scrolling). Hence I have to figure out string size for each view (same bounds).
No, you don't have to figure that out. The text kit stack will do it for you.
In fact, UITextView will flow long text from one text view to another automatically. It's just a matter of configuring the text kit stack — one layout manager with multiple text containers.

iOS - image attachment for the text view changes attributes

I've a UITextView described as follows with the given attributes:
lazy var inputTextView: UITextView = {
let tv = UITextView()
tv.backgroundColor = .white
tv.textContainerInset = UIEdgeInsetsMake(12, 12, 12, 12) // Posicionamento do texto
let spacing = NSMutableParagraphStyle()
spacing.lineSpacing = 4
let attr = [NSParagraphStyleAttributeName : spacing, NSFontAttributeName: UIFont.systemFont(ofSize: 16), NSForegroundColorAttributeName: UIColor.blue]
tv.typingAttributes = attr
return tv
}()
Everything works as expected until I attach an image to the UITextView.
The image gets inserted in the desired position but after its inserted it overrides my textView attributes.
The text becomes small and in a different color than the attributes I've implemented in its declaration.
I'm attaching the image as follows:
let att = NSTextAttachment()
att.image = image
let attrString = NSAttributedString(attachment: att)
self.inputTextView.textStorage.insert(attrString, at: self.currentCursorLocation)
What's causing this issue?
I've even tried to reenforce its attributes whenever I insert an UIImage to its content.
I've tried the following when adding the image:
let att = NSTextAttachment()
att.image = image
let attrString = NSAttributedString(attachment: att)
self.inputTextView.textStorage.insert(attrString, at: self.currentCursorLocation)
let spacing = NSMutableParagraphStyle()
spacing.lineSpacing = 4
let attr = [NSParagraphStyleAttributeName : spacing, NSFontAttributeName: UIFont.systemFont(ofSize: 16), NSForegroundColorAttributeName: UIColor.blue]
self.inputTextView.typingAttributes = attr
And it still doesn't change its attributes.
Whats causing this issue? Any tip?
Thanks
Edit
As suggested here's how I'm setting the cursor position
func textViewDidChange(_ textView: UITextView) {
currentCursorLocation = textView.selectedRange.location
}
I do this to insert the image at the current location of the text blinking cursor
[Edit: Unfortunately this does not solve Ivan's problem - I leave the answer because it is interesting detail for those who do not understand Unicode character encoding].
String range specification is non-intuitive due to the subtleties of Unicode. I expect your issue is that the cursor position at which you are inserting your image is not where you think it is relative to the text and you are inserting the image at a Unicode scalar position that is not between Unicode code points, such that you are corrupting a unicode code. To understand why this can happen, see this Apple article.
Strings in Swift 2
I would suggest using the following notation when specifying string ranges (taken from this Stack Overflow answer: NSAttributedString and emojis: issue with positions and lengths).
// Convert to NSRange by computing the integer distances:
let nsRange = NSRange(location: text.utf16.distance(from: text.utf16.startIndex, to: from16),
length: text.utf16.distance(from: from16, to: to16))
However without seeing how you set your cursor position, it is not possible for me to be sure this is the source of your problem. [Update: thanks for updating the question to show the cursor position - we got there in the end but for others, note, after setting the cursor position this way (which would have been fine), he was incrementing it by 1, which meant the issue I have referred to about Unicode scalars versus code points was in fact the issue].

Resources