Superscript string in Swift - swift

I'm trying to make my text more readable to the user in my app. I'm reading my text from a JSON from "https://api.myjson.com/bins/ss5jb". Is there a way to superscript the verseNumber and change its color to a light gray in my collectionView enumerated string?
And is an enumerated string the best way to pair the verseNumber and verse?
import UIKit
class PageCell: UICollectionViewCell {
let textLabel: UITextView = {
let label = UITextView()
//Custom Font//
guard let customFont = UIFont(name: "CormorantGaramond-Medium", size: 20) else {
fatalError("""
Failed to load the "RCormorantGaramond-Medium" font.
Make sure the font file is included in the project and the font name is spelled correctly.
"""
)
}
//End Custom Font//
label.font = customFont
label.text = ""
label.translatesAutoresizingMaskIntoConstraints = false
label.isEditable = false
label.isUserInteractionEnabled = false
return label
}()
override init(frame: CGRect) {
super.init(frame: frame)
addSubview(textLabel)
//textLabel Constraints
textLabel.topAnchor.constraint(equalTo: self.topAnchor, constant: 40).isActive = true
textLabel.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
textLabel.rightAnchor.constraint(equalTo: self.rightAnchor, constant: -15).isActive = true
textLabel.leftAnchor.constraint(equalTo: self.leftAnchor, constant: 30).isActive = true
}
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let pageCell = collectionView.dequeueReusableCell(withReuseIdentifier: "cellId2", for: indexPath) as! PageCell
let page = book?.pages[indexPath.item]
if let page = page {
let enumeratedPage = page.text.enumerated()
pageCell.textLabel.text = enumeratedPage.reduce("") { (result: String, textPair) -> String in
let result = "\(result)\n"
let verseNumber = "\(textPair.offset + 1) "
let verse = "\(textPair.element)"
return result + verseNumber + verse
}
}
return pageCell
}

There are Two Ways:
Way 1:- NSAttributed String
let mainText = "Here is a example of attributedString"
let attributeText = "attributedString"
let range = (mainText as NSString).range(of: attributeText)
let attributedString = NSMutableAttributedString(string:mainText)
attributedString.addAttribute(NSAttributedStringKey.foregroundColor, value: UIColor.red, range: range)
attribute.addAttribute(NSFontAttributeName, value: UIFont.systemFont(ofSize: 14) , range: range)
lblTitle.attributedText = attributedString
Way :- 2 You can use HTML Property:
let htmlString = "<font color=\"red\">This is </font> <font color=\"blue\"> some text!</font>"
let encodedData = htmlString.data(using: String.Encoding.utf8)!
let attributedOptions = [NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType]
do {
let attributedString = try NSAttributedString(data: encodedData, options: attributedOptions, documentAttributes: nil)
label.attributedText = attributedString
} catch _ {
print("Cannot create attributed String")
}

Use NSAttributedStrings for this. You can change text colors, baseline offsets and fonts to achieve your desired effect.

Related

Swift UIText View replaceCharacters Out of bounds

I try to replace link to image and show on TextView
This my code
class ViewController: UIViewController {
let textView: UITextView = {
let view = UITextView()
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(textView)
textView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
textView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
textView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
textView.topAnchor.constraint(equalTo: view.topAnchor, constant: 50).isActive = true
let input = "New iPhone \nhttp://cdn2.gsmarena.com/vv/bigpic/apple-iphone-6s1.jpg \nTest Test Test"
imageText(text: input)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func imageText(text:String) {
let attributedString = NSMutableAttributedString(string: text)
let detector = try? NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue)
let matches = detector?.matches(in: text, options: .reportCompletion, range:NSRange(location: 0, length: text.count))
for match in matches! {
if match.url?.absoluteString.suffix(3) == "jpg" {
let textAttachment = NSTextAttachment()
let data = NSData(contentsOf: (match.url!))
if data != nil{
let image = UIImage(data: data! as Data)
textAttachment.image = image
let attributedStringWithImage = NSAttributedString(attachment: textAttachment)
attributedString.replaceCharacters(in: (match.range), with: attributedStringWithImage)
}
}
}
textView.attributedText = attributedString
}
}
It works:
App Screen Shot
But if I add a second link, then it crashes.
let input = "New iPhone \nhttp://cdn2.gsmarena.com/vv/bigpic/apple-iphone-6s1.jpg \nTest Test Test \nhttp://cdn2.gsmarena.com/vv/bigpic/apple-iphone-6s1.jpg \nTest Test Test"
console
reason: 'NSMutableRLEArray replaceObjectsInRange:withObject:length:: Out of bounds'
I find the crash point is this line
attributedString.replaceCharacters(in: (match.range), with: attributedStringWithImage)
How to fix this problem?
The thing is: you are mutating attributedString in this line
attributedString.replaceCharacters(in: (match.range), with: attributedStringWithImage)
So, in the following iteration of the for loop, the range that you are trying to replace doesn't exist anymore.

How to add multi-line text using NSAttributedString to a NSButton?

My application supports multiple languages. I have a Translation object which sets string on NSButton Title. How can I use multiline to set text inside my Button?
I used self.lineBreakMode = .ByWordWrapping but it does not work.
class CustomNSButton: NSButton {
override func viewWillDraw() {
let currentText = Translations.shared.current?[self.identifier ?? ""]?.string ?? self.stringValue
self.lineBreakMode = .byWordWrapping
let size = calculateIdealFontSize(min: 5, max: 16)
let translatedString = CustomFormatter.string(for: currentText)
let pstyle = NSMutableParagraphStyle()
pstyle.alignment = .center
let translatedAttributedString = CustomFormatter.attributedString(for: translatedString ?? "", withDefaultAttributes:[NSFontAttributeName : NSFont(name: (self.font?.fontName)!, size: CGFloat(size))!, NSParagraphStyleAttributeName : pstyle])!
attributedTitle = translatedAttributedString
}
}
I created a multiline text by using let textLabel = NSTextField() and a let textFieldCell = CustomNSTextFieldCell() subclass. Add subview in CustomNSButton class addSubview(textLabel)
class CustomNSButton: NSButton {
let textLabel = NSTextField()
let textFieldCell = CustomNSTextFieldCell()
override func viewWillDraw() {
textLabel.frame = CGRect(x:0,y:0, width: frame.width - 2, height: frame.height - 2)
let pstyle = NSMutableParagraphStyle()
pstyle.alignment = .center
textLabel.attributedStringValue = CustomFormatter.attributedString(for: translatedString ?? "", withDefaultAttributes:[NSFontAttributeName : NSFont(name: (self.font?.fontName)!, size: CGFloat(size))!, NSParagraphStyleAttributeName : pstyle, NSForegroundColorAttributeName : NSColor.white])!
textLabel.isEditable = false
textLabel.isBezeled = false
textLabel.backgroundColor = NSColor.clear
textLabel.cell = textFieldCell
addSubview(textLabel)
}
}
class CustomNSTextFieldCell: NSTextFieldCell {
override func drawInterior(withFrame cellFrame: NSRect, in controlView: NSView) {
let attrString: NSAttributedString? = attributedStringValue
attrString?.draw(with: titleRect(forBounds: cellFrame), options: [.truncatesLastVisibleLine, .usesLineFragmentOrigin])
}
override func titleRect(forBounds theRect: NSRect) -> NSRect {
var tFrame: NSRect = super.titleRect(forBounds: theRect)
let attrString: NSAttributedString? = attributedStringValue
let tRect: NSRect? = attrString?.boundingRect(with: tFrame.size, options: [.truncatesLastVisibleLine, .usesLineFragmentOrigin])
if (textRect?.size.height)! < tFrame.size.height {
tFrame.origin.y = theRect.origin.y + (theRect.size.height - (textRect?.size.height)!) / 2.0
tFrame.size.height = (textRect?.size.height)!
}
return tFrame
}
}

How to use a NSNumberFormatter with an AttributedString?

I have been unable to find anything that works on the subject of using an attributed text in a NSTextField with a NumberFormatter. What I want to accomplish is very simple. I would like to use a NumberFormatter on an editable NSTextField with attributed text and keep the text attributed.
Currently, I have subclassed NSTextFieldCell and implemented it as so:
class AdjustTextFieldCell: NSTextFieldCell {
required init(coder: NSCoder) {
super.init(coder: coder)
let attributes = makeAttributes()
allowsEditingTextAttributes = true
attributedStringValue = AttributedString(string: stringValue, attributes: attributes)
//formatter = TwoDigitFormatter()
}
func makeAttributes() -> [String: AnyObject] {
let style = NSMutableParagraphStyle()
style.minimumLineHeight = 100
style.maximumLineHeight = 100
style.paragraphSpacingBefore = 0
style.paragraphSpacing = 0
style.alignment = .center
style.lineHeightMultiple = 1.0
style.lineBreakMode = .byTruncatingTail
let droidSansMono = NSFont(name: "DroidSansMono", size: 70)!
return [NSParagraphStyleAttributeName: style, NSFontAttributeName: droidSansMono, NSBaselineOffsetAttributeName: -60]
}
}
This implementation adjusts the text in the NSTextField instance to have the shown attributes. When I uncomment the line that sets the formatter property the NSTextField loses its attributes. My NumberFormatter is as follows:
class TwoDigitFormatter: NumberFormatter {
override init() {
super.init()
let customAttribs = makeAttributes()
textAttributesForNegativeValues = customAttribs.attribs
textAttributesForPositiveValues = customAttribs.attribs
textAttributesForZero = customAttribs.attribs
textAttributesForNil = customAttribs.attribs
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
let maxLength = 2
let wrongCharacterSet = CharacterSet(charactersIn: "0123456789").inverted
override func isPartialStringValid(_ partialString: String, newEditingString newString: AutoreleasingUnsafeMutablePointer<NSString?>?, errorDescription error: AutoreleasingUnsafeMutablePointer<NSString?>?) -> Bool {
if partialString.characters.count > maxLength {
return false
}
if partialString.rangeOfCharacter(from: wrongCharacterSet) != nil {
return false
}
return true
}
override func attributedString(for obj: AnyObject, withDefaultAttributes attrs: [String : AnyObject]? = [:]) -> AttributedString? {
let stringVal = string(for: obj)
guard let string = stringVal else { return nil }
let customAttribs = makeAttributes()
var attributes = attrs
attributes?[NSFontAttributeName] = customAttribs.font
attributes?[NSParagraphStyleAttributeName] = customAttribs.style
attributes?[NSBaselineOffsetAttributeName] = customAttribs.baselineOffset
return AttributedString(string: string, attributes: attributes)
}
func makeAttributes() -> (font: NSFont, style: NSMutableParagraphStyle, baselineOffset: CGFloat, attribs: [String: AnyObject]) {
let style = NSMutableParagraphStyle()
style.minimumLineHeight = 100
style.maximumLineHeight = 100
style.paragraphSpacingBefore = 0
style.paragraphSpacing = 0
style.alignment = .center
style.lineHeightMultiple = 1.0
style.lineBreakMode = .byTruncatingTail
let droidSansMono = NSFont(name: "DroidSansMono", size: 70)!
return (droidSansMono, style, -60, [NSParagraphStyleAttributeName: style, NSFontAttributeName: droidSansMono, NSBaselineOffsetAttributeName: -60])
}
}
As you can see from the code directly above I have tried:
Setting the textAttributesFor... properties.
Overriding attributedString(for obj: AnyObject, withDefaultAttributes attrs: [String : AnyObject]? = [:])
I have tried both these solution separately from each other and together, of which none of the attempts worked.
TLDR: Is it possible to use attributed text and a NumberFormatter at the same time? If so, how? If not, how can I limit a NSTextField with attributed text to digits only and two characters without using a NumberFormatter?
For anyone in the future who wants to achieve the behavior I was able to do it by subclassing NSTextView and essentially faking a NSTextField like so:
class FakeTextField: NSTextView {
required init?(coder: NSCoder) {
super.init(coder: coder)
delegate = self
let droidSansMono = NSFont(name: "DroidSansMono", size: 70)!
configure(lineHeight: frame.size.height, alignment: .center, font: droidSansMono)
}
func configure(lineHeight: CGFloat, alignment: NSTextAlignment, font: NSFont) {
//Other Configuration
//Define and set typing attributes
let style = NSMutableParagraphStyle()
style.minimumLineHeight = lineHeight
style.maximumLineHeight = lineHeight
style.alignment = alignment
style.lineHeightMultiple = 1.0
typingAttributes = [NSParagraphStyleAttributeName: style, NSFontAttributeName: font]
}
}
extension TextField: NSTextViewDelegate {
func textView(_ textView: NSTextView, shouldChangeTextIn affectedCharRange: NSRange, replacementString: String?) -> Bool {
if let oldText = textView.string, let replacement = replacementString {
let newText = (oldText as NSString).replacingCharacters(in: affectedCharRange, with: replacement)
let numberOfChars = newText.characters.count
let wrongCharacterSet = CharacterSet(charactersIn: "0123456789").inverted
let containsWrongCharacters = newText.rangeOfCharacter(from: wrongCharacterSet) != nil
return numberOfChars <= 2 && !containsWrongCharacters
}
return false
}
}
With this class, all you need to do is set the NSTextView in your storyboard to this class and change the attributes and shouldChangeTextIn to suit your needs. In this implementation, the "TextField" has various attributes set and is limited to two characters and only digits.
You subclass NSNumberFormatter with the following:
final class ChargiePercentageNumberFormatter: NumberFormatter {
override func attributedString(for obj: Any, withDefaultAttributes attrs: [NSAttributedString.Key : Any]? = nil) -> NSAttributedString? {
guard let obj = obj as? NSNumber else {
return nil;
}
guard let numberString = self.string(from: obj) else {
return nil
}
let font = NSFont(name: "Poppins-Regular", size: 16)!
let attrs: [NSAttributedString.Key: Any] = [.font: font, .kern: 1.2]
let result = NSAttributedString(string: numberString, attributes: attrs)
return result
}
}

swift UIDatePicker change font, font color, font size etc

I've been trying to change font in native UIDatePicker in iOS and I did it, but with some unsettled details:
I use extension for UIDatePicker to change font in it's labels:
extension UIDatePicker {
func stylizeView(view: UIView? = nil) {
let view = view ?? self
for subview in view.subviews {
if let label = subview as? UILabel {
if let text = label.text {
print("UIDatePicker :: sylizeLabel :: \(text)\n")
label.font = UIFont(name: "MyriadPro-Light", size: 17)!
}
} else { stylizeView(subview) }
}
}}
So, you can customize font deeply:
struct DatePickerStyle {
let tintColor = UIColor(hex: 0xFFFFFF)
let font = UIFont(name: "MyriadPro-Light", size: 17)!
let fontColor = UIColor(hex: 0x000000)
let fontKern: CGFloat = 0.2
var paragraphStyle: NSMutableParagraphStyle {
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.lineSpacing = 4
paragraphStyle.lineHeightMultiple = 1
paragraphStyle.alignment = .Right
return paragraphStyle
}}
extension UIDatePicker {
func stylizeView(view: UIView? = nil) {
let style = DatePickerStyle()
let view = view ?? self
for subview in view.subviews {
if let label = subview as? UILabel {
if let text = label.text {
print("UIDatePicker :: sylizeLabel :: \(text)\n")
let attributedString = NSMutableAttributedString(string: text)
let attributedStringRange = NSMakeRange(0, attributedString.length)
attributedString.addAttributes([
NSParagraphStyleAttributeName: style.paragraphStyle,
NSFontAttributeName: style.font,
NSForegroundColorAttributeName: style.fontColor,
NSKernAttributeName: style.fontKern
], range: attributedStringRange)
//label.font = style.font
label.tintColor = style.fontColor
label.attributedText = attributedString
}
} else { stylizeView(subview) }
}
}
}
This function in extension is implemented on any Control Events of UIDatePicker:
datePicker.addTarget(self, action: #selector(CellWithDatePicker.updateDatePickerStyle), forControlEvents: .AllEvents)
&
func updateDatePickerStyle() {
print(":: updateDatePickerStyle")
datePicker.stylizeView()
}
Problem 1:
When I init UIDatePicker, font of the picker is still SanFrancisco.
But when I change value in UIDatePicker the font is changed to my font
My Font
I tried to implement datePicker.stylizeView() or self.stylizeView() on every stage of UIDatePicker lifecycle, but it can only change selected line of DatePicker.
Problem 2:
While I rotating DatePicker after pic.2 when all label of DatePicker is set with newFont, new labels which is outside the selected line is still with old font (SanFrancisco). And when I stop rotating DatePicker all label is updated to newFont.
enter image description here
Any idea how to fix it?
Use GCD with an interval of 0.1 to call the styling function.
Example:
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1)
{
self.datePicker.stylizeView();ss
}
The only way for changing the font of UIDatePickerView (until now) is swizzling:
you can change the font by an extension of UILabel! (this is not recommended but it works!)
import Foundation
import UIKit
public extension UILabel {
#objc func setFontSwizzled(font: UIFont) {
if self.shouldOverride() {
self.setFontSwizzled(font: UIFont.fontWith(style: .regular, size: 14))
} else {
self.setFontSwizzled(font: font)
}
}
private func shouldOverride() -> Bool {
let classes = ["UIDatePicker", "UIDatePickerWeekMonthDayView", "UIDatePickerContentView"]
var view = self.superview
while view != nil {
let className = NSStringFromClass(type(of: view!))
if classes.contains(className) {
return true
}
view = view!.superview
}
return false
}
private static let swizzledSetFontImplementation: Void = {
let instance: UILabel = UILabel()
let aClass: AnyClass! = object_getClass(instance)
let originalMethod = class_getInstanceMethod(aClass, #selector(setter: font))
let swizzledMethod = class_getInstanceMethod(aClass, #selector(setFontSwizzled))
if let originalMethod = originalMethod, let swizzledMethod = swizzledMethod {
// switch implementation..
method_exchangeImplementations(originalMethod, swizzledMethod)
}
}()
static func swizzleSetFont() {
_ = self.swizzledSetFontImplementation
}
}
and for changing the color you just simply call the function below:
datePicker.setValue(UIColor.whiteColor(), forKeyPath: "textColor")
if it's necessary to be re-rendered you need to call:
datePicker.datePickerMode = .CountDownTimer
datePicker.datePickerMode = .DateAndTime //or whatever your original mode was

TableView cell messing up constraints

I've a tableView where I present news feed from my Database. The amount of cells right now is around 20 but it will continue to grow for sure.
In a cell I've three labels: Title, Date and a short description. I've set up all of my constraints using storyboards. And the height of my tableView cell is dynamically changing according to the size of my content.
The problem is whenever I load my feed, one of the cells is always messed up. There is a constraint between Title label and date label >=30 so it's always at least one cell that will have a title label with a constant width of 190 (the others are always correctly displayed). If I scroll my tableView up and return back to that cell it will come back to normal but the height of the cell will stay the same (too big for the content).
What can be causing the problem?
Thank you in advance!
Edit:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
//let cell = tableView.dequeueReusableCellWithIdentifier("Cell1", forIndexPath: indexPath)
let cell = tableView.dequeueReusableCellWithIdentifier("NewsCell", forIndexPath: indexPath) as! FinalNews
cell.backgroundColor = UIColor(red: 249/255, green: 237/255, blue: 229/255, alpha: 1)
let chevron = UIImage(named: "Next4.png")
cell.accessoryType = .DisclosureIndicator
cell.accessoryView = UIImageView(image: chevron!)
cell.textLabel?.textColor = UIColor.blackColor()
// Feeds dictionary.
var dict : NSDictionary! = myFeed.objectAtIndex(indexPath.row) as! NSDictionary
let firstiteration: String = (myFeed.objectAtIndex(indexPath.row).objectForKey("Description")?.stringByReplacingOccurrencesOfString("<b>", withString: ""))!
let seconditeration = firstiteration.stringByReplacingOccurrencesOfString("<[^>]+>", withString: "", options: .RegularExpressionSearch, range: nil)
let category: String = myFeed[indexPath.row].objectForKey("CategoryID") as! String
func getCategory(value: String) -> String {
var end = value.endIndex
for var i = value.startIndex;
i < value.endIndex;
i = i.successor() {
if value[i] == "\n" {
end = i
break
}
}
return value[value.startIndex..<end]
}
let counter1 = getCategory(category)
if counter1 == "1"{
cell.titleLabel.text = myFeed.objectAtIndex(indexPath.row).objectForKey("DosNum") as? String
cell.descLabel.text = " \n "
cell.descLabel.textColor = UIColor(red: 249/255, green: 237/255, blue: 229/255, alpha: 1)
cell.dateLabel.text = myFeed.objectAtIndex(indexPath.row).objectForKey("Date_text") as? String
}
else{
cell.titleLabel.text = myFeed.objectAtIndex(indexPath.row).objectForKey("Title") as? String
cell.descLabel.text = seconditeration
cell.descLabel.textColor = UIColor.blackColor()
cell.dateLabel.text = myFeed.objectAtIndex(indexPath.row).objectForKey("Date_text") as? String
}
var shorDate: String {
let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd"
return dateFormatter.stringFromDate(NSDate())
}
let today1 = shorDate
let dateIter = myFeed.objectAtIndex(indexPath.row).objectForKey("Date_publication") as? String
let dateIter1: String = (dateIter as? NSString)!.substringToIndex(10)
let calendar = NSCalendar.currentCalendar()
//let twoDaysAgo = calendar.dateByAddingUnit(.Day, value: -1, toDate: NSDate(), options: [])
var shorDate1: String {
let dateFormatter = NSDateFormatter()
let oneDaysAgo = calendar.dateByAddingUnit(.Day, value: -1, toDate: NSDate(), options: [])
dateFormatter.dateFormat = "yyyy-MM-dd"
return dateFormatter.stringFromDate(oneDaysAgo!)
}
let oneDaysAgo1 = shorDate1
if today1 == dateIter1{
cell.dateLabel.text = "Сегодня"
//someDate is berofe than today
} else if dateIter1 == oneDaysAgo1 {
//someDate is equal or after than today
cell.dateLabel.text = "Вчера"
} else {
cell.dateLabel.text = myFeed.objectAtIndex(indexPath.row).objectForKey("Date_text") as? String
}
return cell
}