NSTextView: apply paragraph style - swift

I'm trying to apply the default paragraph style to a NSTextView but it doesn't seem to work. Any idea?
var paragraphStyle:NSMutableParagraphStyle = NSMutableParagraphStyle();
paragraphStyle.lineSpacing = 100.0;
paragraphStyle.firstLineHeadIndent = 100.0;
WLMainEditor.defaultParagraphStyle = paragraphStyle;

I find that it always works best to start with an existing paragraph style and make changes to it. If you don't have one to start with, use NSParagraphStyle.default()
let paragraphStyle = NSParagraphStyle.default().mutableCopy() as! NSMutableParagraphStyle
paragraphStyle.lineSpacing = 100
paragraphStyle.firstLineHeadIndent = 100
WLMainEditor.defaultParagraphStyle = paragraphStyle
Also, at least in this code snippet, the paragraphStyle can (and should be) a let instead of a var since it's an Objective-C mutable object and not a Swift mutable collection.

Related

Set NSTextView to wrap by char either programmatically or through xcode?

There are a couple of solutions online but they are either 10 years old and no longer work or are written in objective C (And also very outdated). One solution involved AttributedText and changing the line break style every single time the content is updated, which for me would be hundreds of calls every few minutes. Surely this can't be the best way. I must be missing something here, there must be a way to set an NSTextView to wrap by char permanently.
I've tried this but it doesn't work and continues to wrap by word:
consoleTextView.textContainer?.lineBreakMode = NSLineBreakMode.byCharWrapping
I've also tried using the attributes inspector in xcode. I finally found the option for it but clicking it does nothing, it just continues to say 'empty selection' in the 'line breaking' field:
You are supposed to set the line break modes on the paragraph styles. This is the smallest possible example to make it work:
let textView = NSTextView(frame: CGRect(x: 0, y: 0, width: 100, height: 200))
let paragraphStyle = NSMutableParagraphStyle.default.mutableCopy() as! NSMutableParagraphStyle
paragraphStyle.lineBreakMode = .byCharWrapping
textView.defaultParagraphStyle = paragraphStyle
textView.string = "12345 12345 12345"
view.addSubview(textView)
Anyone looking for an objective-c answer would use something similar to:
NSMutableParagraphStyle * paragraphStyle = [[NSMutableParagraphStyle alloc] init];
paragraphStyle.lineBreakMode = NSLineBreakByCharWrapping;
myTextView.defaultParagraphStyle = paragraphStyle;

How to make `attributedReadMoreText` in `ReadMoreTextView` library not selectable - swift

I am a new iOS programming. Now i am creating a sample app which display text using ReadMoreTextView library. My content may contain many lines but by using this library i can maximumNumberOfLines to display how many lines of content should be displayed. I implement those content in cell of UITableView and i have problem is that, when i use label.attributedReadMoreText = NSAttributedString(string: "...") then end of content will display ... and when i click on it and then whole content will be display so, my question is that: How to not letting user click on that ... because i want user to click on cell then i will show another view and display whole content there?
How can i achieve something like this? Thank in advance.
This is how i set UITextView
lazy var categoryShortDetailLabel: ReadMoreTextView = {
let label = ReadMoreTextView()
label.font = UIFont(name: "SFCompactText-Regular", size: 16)
label.textColor = .black
label.isEditable = false
label.isSelectable = false
label.maximumNumberOfLines = 3
label.shouldTrim = true
label.attributedReadMoreText = NSAttributedString(string: "...")
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
Looking at the code here, I found that, the ReadMoreTextView is meant to provide you the feature like, ReadMore and ReadLess for the larger texts in textView.
However your requirement is to stop that functionality. Now, if you take a look at the code here, you will get the idea, that the function shoreMoreText and it's a private function so, can't override it. and this function is expanding the texts and setting the numberOfLines to zero. so, what you can do is, comment the code within and return from function to stop doing the action. Also as the ReadMoreTextView is Licensed as MIT(Read licence here) so, it's okay to modify the code.
private func showMoreText() {
return
/*if let readLessText = readLessText, text.hasSuffix(readLessText) { return }
shouldTrim = false
textContainer.maximumNumberOfLines = 0
if let originalAttributedText = _originalAttributedText?.mutableCopy() as? NSMutableAttributedString {
attributedText = _originalAttributedText
let range = NSRange(location: 0, length: text.count)
if let attributedReadLessText = attributedReadLessText {
originalAttributedText.append(attributedReadLessText)
}
textStorage.replaceCharacters(in: range, with: originalAttributedText)
}
invalidateIntrinsicContentSize()
invokeOnSizeChangeIfNeeded()*/
}
Try and share your results.
Hope it helps!

How to get the selected string from a NSTextView in Swift?

How to do to get the selected string from a NSTextView in Swift?
// create a range of selected text
let range = mainTextField.selectedRange()
// this works but I need a plain string not an attributed string
let str = mainTextField.textStorage?.attributedSubstring(from: range)
Maybe I have to add an intermediate step where I get the full string and then apply the range on it?
What about
let str = mainTextField.text.substring(with: range)
Edit:
This should work now:
let range = mainTextField.selectedRange() // Returns NSRange :-/ (instead of Range)
let str = mainTextField.string as NSString? // So we cast String? to NSString?
let substr = str?.substring(with: range) // To be able to use the range in substring(with:)
The code snippet in the Swift 5. That's not very hard but repeated
let string = textView.attributedString().string
let selectedRange = textView.selectedRange()
let startIndex = string.index(string.startIndex, offsetBy: selectedRange.lowerBound)
let endIndex = string.index(string.startIndex, offsetBy: selectedRange.upperBound)
let substring = textView.attributedString().string[startIndex..<endIndex]
let selectedString = String(substring)
In case anyone is having issues with the selectedRanges() not returning the proper range for an NSTextView, make sure that you have made your NSTextView selectable. I don't know if it's not selectable by default but I had to enable it;
textView.isSelectable = true
I was trying this in Swift 5 using #user25917 code sample above and I couldn't get the ranges no matter what. I was getting a visual confirmation the text was selected through highlighting which threw me off. This was driving me mad for a few hours.
This may be helpfull for you :
let textView = NSTextView(frame: NSMakeRect(0, 0, 100, 100))
let attributes = [NSForegroundColorAttributeName: NSColor.redColor(),
NSBackgroundColorAttributeName: NSColor.blackColor()]
let attrStr = NSMutableAttributedString(string: "my string", attributes: attributes)
let area = NSMakeRange(0, attrStr.length)
if let font = NSFont(name: "Helvetica Neue Light", size: 16) {
attrStr.addAttribute(NSFontAttributeName, value: font, range: area)
textView.textStorage?.appendAttributedString(attrStr)
}

tvOS: Anyway to display a subtitle outside of the AVPlayer?

So the scenario is that there is a view where the user can enable/disable subtitles in an app I'm helping to develop.
On that view there is a sample text saying "This is what captions look like", and at the moment it's just a basic, unstyled UILabel. Ideally I would like it to be styled in a similar manner to how the user has customized their captions in the System Settings.
Is this possible in any way? I've envisioned two possible method:
Create an AVPlayer instance and a .vtt file with the text, load it into the view and pause the player. I'm not sure this is possible with a sample video (and it would somehow have to be transparent as there is an image behind the sample sub text).
Somehow get all the styling (font, size, background color, etc) the user has set for their subtitle and create an attributed string to match that
Method 2 seems like the most feasible way, but I don't know if we have access to those settings in code.
So I figured it out! It basically makes use a combination of the Media Accessibility API, which allows you to get the values the user has chosen for their captions/subtitle settings, Attributed Strings, and a subclass UILabel (although this could maybe be substituted with a UITextView as that will allow you to set it's UIEdgeInsets natively)
So, first, the subclass is to allow the UILabel to be inset. This is because captions can have a background color AND a text highlight color and without the inset, the text highlight is all you see. So the function the subclass is simple:
class InsetUILabel: UILabel {
override func drawTextInRect(rect: CGRect) {
let inset: CGFloat = 15
let insets: UIEdgeInsets = UIEdgeInsets(top: inset, left: inset/2, bottom: inset, right: inset/2)
super.drawTextInRect(UIEdgeInsetsInsetRect(rect, insets))
}
}
And for generating the actual label. This uses a label called textSample, but you can obviously make it a little more general.
import MediaAccessibility
func styleLabel(sampleText: String) {
let domain = MACaptionAppearanceDomain.User
// Background styling
let backgroundColor = UIColor(CGColor: MACaptionAppearanceCopyWindowColor(domain, nil).takeRetainedValue())
let backgroundOpacity = MACaptionAppearanceGetWindowOpacity(domain, nil)
textSample.layer.backgroundColor = backgroundColor.colorWithAlphaComponent(backgroundOpacity).CGColor
textSample.layer.cornerRadius = MACaptionAppearanceGetWindowRoundedCornerRadius(domain, nil)
// Text styling
var textAttributes = [String:AnyObject]()
let fontDescriptor = MACaptionAppearanceCopyFontDescriptorForStyle(domain, nil, MACaptionAppearanceFontStyle.Default).takeRetainedValue()
let fontName = CTFontDescriptorCopyAttribute(fontDescriptor, "NSFontNameAttribute") as! String
let fontColor = UIColor(CGColor: MACaptionAppearanceCopyForegroundColor(domain, nil).takeRetainedValue())
let fontOpacity = MACaptionAppearanceGetForegroundOpacity(domain, nil)
let textEdgeStyle = MACaptionAppearanceGetTextEdgeStyle(domain, nil)
let textHighlightColor = UIColor(CGColor: MACaptionAppearanceCopyBackgroundColor(domain, nil).takeRetainedValue())
let textHighlightOpacity = MACaptionAppearanceGetBackgroundOpacity(domain, nil)
let textEdgeShadow = NSShadow()
textEdgeShadow.shadowColor = UIColor.blackColor()
let shortShadowOffset: CGFloat = 1.5
let shadowOffset: CGFloat = 3.5
switch(textEdgeStyle) {
case .None:
textEdgeShadow.shadowColor = UIColor.clearColor()
case .DropShadow:
textEdgeShadow.shadowOffset = CGSize(width: -shortShadowOffset, height: shortShadowOffset)
textEdgeShadow.shadowBlurRadius = 6
case .Raised:
textEdgeShadow.shadowOffset = CGSize(width: 0, height: shadowOffset)
textEdgeShadow.shadowBlurRadius = 5
case .Depressed:
textEdgeShadow.shadowOffset = CGSize(width: 0, height: -shadowOffset)
textEdgeShadow.shadowBlurRadius = 5
case .Uniform:
textEdgeShadow.shadowColor = UIColor.clearColor()
textAttributes[NSStrokeColorAttributeName] = UIColor.blackColor()
textAttributes[NSStrokeWidthAttributeName] = -2.0
default:
break
}
textAttributes[NSFontAttributeName] = UIFont(name: fontName, size: (textSample.font?.pointSize)!)
textAttributes[NSForegroundColorAttributeName] = fontColor.colorWithAlphaComponent(fontOpacity)
textAttributes[NSShadowAttributeName] = textEdgeShadow
textAttributes[NSBackgroundColorAttributeName] = textHighlightColor.colorWithAlphaComponent(textHighlightOpacity)
textSample.attributedText = NSAttributedString(string: sampleText, attributes: textAttributes)
}
Now the text highlight section makes use of shadows, with values I think look pretty good, but you might want to tweak them a tiny bit. Hope this helps!

How can I vertically align my status bar item text?

Left one on the picture is the date and time from Apple and one is my application one. As you may see the text of my application appears lower than the Apple one. Which doesn't look pretty. How can this be resolved?
self.statusBarItem.image = nil
self.statusBarItem.button?.imagePosition = .NoImage
self.statusBarItem.button?.title = "Sun 5 Jun 13:35"
You could do it in offset. You cannot do it like this since it needs to be done on the status item button bar.
button.frame = CGRectMake(0.0, 0.5, button.frame.width, button.frame.height)
I wanted to make a quick addition to this. While the accepted answer does indeed work based on the context of the question. It will not work if you have an image, as this moves everything (image and text). I have found for some reason, the image is vertically centered, but the text is not.
What you actually want to do in that case is set NSAttributedString.Key.baselineOffset, where you derive the baseline value as follows
var displayScale: CGFloat = 1.0
let pstyle = NSMutableParagraphStyle()
pstyle.alignment = .center;
let aStr = NSAttributedString(string: str, attributes: [NSAttributedString.Key.paragraphStyle: pstyle, NSAttributedString.Key.font: font])
if let main = NSScreen.main {
displayScale = main.backingScaleFactor
if displayScale <= 0.0 {
displayScale = 1.0
}
}
let textHeight = aStr.size().height
baselineOffset = ((font.ascender - font.descender) - textHeight) / displayScale
button.attributedTitle = = NSAttributedString(string: str, attributes: [NSAttributedString.Key.paragraphStyle: pstyle, NSAttributedString.Key.baselineOffset: baselineOffset, NSAttributedString.Key.font: font])
Disclaimers here are that I'm not completely sure about the displayScale part. I theorize this is needed to correct for pixels/points.
I've only added this answer because I was struggling to find a good answer for this. I got my inspiration to do this based on this: Center two fonts with different different sizes vertically in an NSAttributedString
If this technique is wrong, I'd be interested to know so I can fix it.