Swift ---UIView addSubView(UItextView),nothing can be displayed - uiview

class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
//let attstring = NSMutableAttributedString
let testTextView = UITextView.init(frame: CGRect.init(x: 0, y: 0, width: 100, height: 100))
let attstring = NSMutableAttributedString.init(string: "You can check flights from the past 4 days, or plan 3 days ahead.\n\nTo find out when and where we fly for the rest of the year, please view our Timetable.", attributes: [NSFontAttributeName:UIFont.boldSystemFont(ofSize: 12),NSForegroundColorAttributeName:UIColor.red])
attstring.setAttributes([NSForegroundColorAttributeName:UIColor.blue], range: NSRange.init(location: attstring.length-10, length: 9))
print("test test!")
self.view.addSubview(testTextView)
}
There is nothing displayed,what is the problem!?

In swift 3 you should write this:
let testTextView = UITextView.init(frame: CGRect.init(x: 0, y: 0, width: 100, height: 100))
let attstring = NSMutableAttributedString.init(string: "You can check flights from the past 4 days, or plan 3 days ahead.\n\nTo find out when and where we fly for the rest of the year, please view our Timetable.", attributes: [NSFontAttributeName:UIFont.boldSystemFont(ofSize: 12),NSForegroundColorAttributeName:UIColor.red])
testTextView.attributedText = attstring
attstring.setAttributes([NSForegroundColorAttributeName:UIColor.blue], range: NSRange.init(location: attstring.length-10, length: 9))
self.view.addSubview(testTextView)

Related

How do you bold the first line in an NSMutableParagraphStyle?

I have a class called "rectangle" to make custom UILabels. I override "draw" in the rectangle class. When I instantiate the label, I want the FIRST line of text to show up in bolded font. I know how to solve this by manually getting the range for each string... however, I have more than 300 strings to do. The strings are currently in an array, formatted like so: "Happy \n Birthday". How can I make the word "Happy" bold?
var messageText = "Happy \n Birthday"
let rectanglePath = UIBezierPath(rect: rectangleRect)
context.saveGState()
UIColor.white.setFill()
rectanglePath.fill()
context.restoreGState()
darkPurple.setStroke()
rectanglePath.lineWidth = 0.5
rectanglePath.lineCapStyle = .square
rectanglePath.lineJoinStyle = .round
rectanglePath.stroke()
let rectangleStyle = NSMutableParagraphStyle()
rectangleStyle.alignment = .center
let rectangleFontAttributes = [
.font: UIFont.myCustomFont(true),
.foregroundColor: UIColor.black,
.paragraphStyle: rectangleStyle,
] as [NSAttributedString.Key: Any]
let rectangleTextHeight: CGFloat = messageText.boundingRect(with: CGSize(width: rectangleRect.width, height: CGFloat.infinity), options: .usesLineFragmentOrigin, attributes: rectangleFontAttributes, context: nil).height
context.saveGState()
context.clip(to: rectangleRect)
messageText.draw(in: CGRect(x: rectangleRect.minX, y: rectangleRect.minY + (rectangleRect.height - rectangleTextHeight) / 2, width: rectangleRect.width, height: rectangleTextHeight), withAttributes: rectangleFontAttributes)
context.restoreGState()
You can find the first by separating the string by newline:
let firstLine = "Happy \n Birthday".split(separator: "\n").first
This will give you the first line of the string. (long text multi lining doesn't count) then you can find the range using this and apply the bold effect.
How this works:
You need to set the label the way that accepts multiline:
Find the range of first line
Convert it to nsRange
Apply attributes to the range
Here is a fully working example:
import UIKit
import PlaygroundSupport
extension StringProtocol where Index == String.Index {
func nsRange(from range: Range<Index>) -> NSRange {
return NSRange(range, in: self)
}
}
class MyViewController : UIViewController {
override func loadView() {
let view = UIView()
view.backgroundColor = .white
let label = UILabel()
label.numberOfLines = 0
label.text = "Happy \n Birthday"
label.textColor = .black
let text = "Happy \n Birthday"
let attributedString = NSMutableAttributedString(string: text)
let firstLine = text.split(separator: "\n").first!
let range = text.range(of: firstLine)!
attributedString.addAttributes([.font : UIFont.boldSystemFont(ofSize: 14)], range: text.nsRange(from: range))
label.attributedText = attributedString
label.sizeToFit()
view.addSubview(label)
self.view = view
}
}
PlaygroundPage.current.liveView = MyViewController()

How to display color picker on button click, and on select of particular color i want to get a hex code of that color?

which is the best cocoapod of color pickets for ios application?
I am trying to implement DRColorPickerWheelView by installing cocoapods in my sample application, next I want to get the RGB value. How to get that - Please do help.
I am implementing as following,
correct me if I am wrong.
import UIKit
import DRColorPicker
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let img = UIImageView()
img.image = DRColorPickerImage("colormap")
img.frame = CGRect(x: 20, y: 90, width: 330, height: 300)
view.addSubview(img)
let v = DRColorPickerWheelView()
v.frame = CGRect(x: 10, y: 40, width: 350, height: 400)
view.addSubview(v)
// Do any additional setup after loading the view, typically from a nib.
}
}
try below code:
let str = yourSelectedColorVariable.hexStringFromColor
This category defined at: DRColorPicker

Emoji support for NSAttributedString attributes (kerning/paragraph style)

I am using a kerning attribute on a UILabel to display its text with some custom letter spacing. Unfortunately, as I'm displaying user-generated strings, I sometimes see things like the following:
ie sometimes some emoji characters are not being displayed.
If I comment out the kerning but apply some paragraph style instead, I get the same kind of errored rendering.
I couldn't find anything in the documentation explicitely rejecting support for special unicode characters. Am I doing something wrong or is it an iOS bug?
The code to reproduce the bug is available as a playground here: https://github.com/Bootstragram/Playgrounds/tree/master/LabelWithEmoji.playground
and copied here:
//: A UIKit based Playground for presenting user interface
import UIKit
import PlaygroundSupport
extension NSAttributedString {
static func kernedSpacedText(_ text: String,
letterSpacing: CGFloat = 0.0,
lineHeight: CGFloat? = nil) -> NSAttributedString {
// TODO add the font attribute
let attributedString = NSMutableAttributedString(string: text)
attributedString.addAttribute(NSAttributedStringKey.kern,
value: letterSpacing,
range: NSRange(location: 0, length: text.count))
if let lineHeight = lineHeight {
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.lineSpacing = lineHeight
attributedString.addAttribute(NSAttributedStringKey.paragraphStyle,
value: paragraphStyle,
range: NSRange(location: 0, length: text.count))
}
return attributedString
}
}
//for familyName in UIFont.familyNames {
// for fontName in UIFont.fontNames(forFamilyName: familyName) {
// print(fontName)
// }
//}
class MyViewController : UIViewController {
override func loadView() {
let view = UIView()
view.backgroundColor = .white
let myString = "1βš½πŸ“ΊπŸ»βšΎοΈπŸŒ―πŸ„β€β™‚οΈπŸ‘\n2 πŸ˜€πŸ’ΏπŸ’Έ 🍻"
let label = UILabel()
label.frame = CGRect(x: 150, y: 200, width: 200, height: 100)
label.attributedText = NSAttributedString.kernedSpacedText(myString)
label.numberOfLines = 0
label.textColor = .black
view.addSubview(label)
self.view = view
}
}
// Present the view controller in the Live View window
PlaygroundPage.current.liveView = MyViewController()
Thanks.
TL, DR:
String.count != NSString.length. Any time you see NSRange, you must convert your String into UTF-16:
static func kernedSpacedText(_ text: String,
letterSpacing: CGFloat = 0.0,
lineHeight: CGFloat? = nil) -> NSAttributedString {
// TODO add the font attribute
let attributedString = NSMutableAttributedString(string: text)
attributedString.addAttribute(NSAttributedStringKey.kern,
value: letterSpacing,
range: NSRange(location: 0, length: text.utf16.count))
if let lineHeight = lineHeight {
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.lineSpacing = lineHeight
attributedString.addAttribute(NSAttributedStringKey.paragraphStyle,
value: paragraphStyle,
range: NSRange(location: 0, length: text.utf16.count))
}
return attributedString
}
The longer explanation
Yours is a common problem converting between Swift's String and ObjC's NSString. The length of a String is the number of extended grapheme clusters; in ObjC, it's the number of UTF-16 code points needed to encode that string.
Take the thumb-up character for example:
let str = "πŸ‘"
let nsStr = str as NSString
print(str.count) // 1
print(nsStr.length) // 2
Things can get even weirder when it comes to the flag emojis:
let str = "πŸ‡ΊπŸ‡Έ"
let nsStr = str as NSString
print(str.count) // 1
print(nsStr.length) // 4
Even though this article was written all the way back in 2003, it's still a good read today:
The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets.

Aligning multiple runtime generated UILabels in a UITableView

I have a UITableView that needs to support content by listing style something like
But the tricky part is that the amount of "Label" will vary with each cell, some may have only 4 but also up to 12. Also the "Value" can be either a single word or short phrase up to two lines(like in the image above). So I decided to use UIStackView to help me pack and size my UILabels used to display this. I am currently stuck at a problem when the "Label" varies in length like:
I need the leading end of the "Values" to be aligned like the first image even though the "Label" vary in length. Is there a behaviour of UIStackView that allows so? Or is there another approach that can allow me to obtain the results I need?
Note: Each "Label" and "Value" is in one UIStackView, I did it to align them.
I tried using String Formatting too, but "Values" with more than one line will wrap under the label instead of wrapping by itself like I manage to do in the images.
I tried placing all "Labels" in one UIStackView and all "Values" in another, I could no get them to align like they do in the images once the "Value" is more than one line.
Or if it might be a mistake I made somewhere, this is how I created the UIStackViews:
var higherCount = 0
if labels.count<values.count {
higherCount = values.count
} else {
higherCount = labels.count
}
mainStackView = UIStackView(frame: CGRect(x: 0, y: 0, width: frame.width, height: frame.height))
for i in 0..<higherCount {
var height:CGFloat = 20
if (values[i] as NSString).size(attributes: [NSFontAttributeName:UIFont.systemFont(ofSize: UIFont.systemFontSize)]).width > frame.width {
height = 42
}
let stackViewToAdd = UIStackView(frame: CGRect(x: 0, y: 0, width: frame.width, height: height))
let label = UILabel(frame: CGRect(x: 0, y: 0, width: frame.width, height: height))
if labels.count<higherCount {
label.text = ""
} else {
label.text = labels[i]
}
label.setContentCompressionResistancePriority(1000, for: .horizontal)
label.setContentHuggingPriority(999, for: .horizontal)
stackViewToAdd.addArrangedSubview(label)
let value = UILabel(frame: CGRect(x: 0, y: 0, width: frame.width, height: height))
if values.count<higherCount {
value.text = ""
} else {
value.text = values[i]
}
value.lineBreakMode = .byWordWrapping
value.numberOfLines = 2
stackViewToAdd.addArrangedSubview(value)
mainStackView?.addArrangedSubview(stackViewToAdd)
}
mainStackView?.alignment = .fill
mainStackView?.axis = .vertical
mainStackView?.distribution = .fill
I you created your cell programmatically, then you can resize the cell programmatically depends on the size of UILabel Content.
In my case Label font is UIFont.systemFont(ofSize: 15), minimum TableViewCell height is 50, arrLabel1 and arrLabel2 will be the content of Labels.
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return arrLable1.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: .default, reuseIdentifier: "\(indexPath.row)")
let lable1 = UILabel(frame: CGRect(x: 10, y: 10, width: view.frame.size.width - 20, height: 0))
lable1.numberOfLines = 0
lable1.font = UIFont.systemFont(ofSize: 15)
lable1.text = arrLable1[indexPath.row]
lable1.sizeToFit()
cell.addSubview(lable1)
let lable2 = UILabel(frame: CGRect(x: 10, y: lable1.frame.origin.y + lable1.frame.size.height + 10 , width: view.frame.size.width - 20, height: 0))
lable2.numberOfLines = 0
lable2.font = UIFont.systemFont(ofSize: 15)
lable2.text = arrLable2[indexPath.row]
lable2.sizeToFit()
cell.addSubview(lable2)
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath)
-> CGFloat {
let boundingRect1 = arrLable1[indexPath.row].boundingRect(with: CGSize(width: view.frame.size.width - 40 , height: CGFloat(MAXFLOAT)), options: NSStringDrawingOptions.usesLineFragmentOrigin,attributes:[ NSFontAttributeName : UIFont.systemFont(ofSize: 15)] ,context: nil)
let boundingRect2 = arrLable2[indexPath.row].boundingRect(with: CGSize(width: view.frame.size.width - 40 , height: CGFloat(MAXFLOAT)), options: NSStringDrawingOptions.usesLineFragmentOrigin,attributes:[ NSFontAttributeName : UIFont.systemFont(ofSize: 15)] ,context: nil)
guard boundingRect1.height + boundingRect2.height + 30 > 50 else {
return 50
}
return boundingRect1.height + boundingRect2.height + 30
}
Set Label numberOfLines to 0

Detail Disclosure loading on top of the previous detail disclosure

I have a map view with annotations and every annotation has a corresponding date with day, month, and time.
I'm pushing the day to the new view controller then using a switch to see what day of the month it falls on and then displaying the corresponding calendar icon. I can't put the func anywhere other than viewDidAppear which cause a brief lag and two, prevents me from using
for view in self.view.subviews {
view.removeFromSuperview()
}
So two questions:
how can I call the dayCalendar function before viewDidAppear (anywhere else I've tried to put the code, it returns a nil)
how do I prevent my detailDisclosures from appearing on top of one another?
This is the code responsible for showing the image:
override func viewDidAppear(animated: Bool) {
dayCalendar()
}
func dayCalendar() {
switch day {
case 1:
let calendarDay = "Calendar 1-50.png"
let calendarDayImage = UIImage(named: calendarDay)
let calendarDayImageView = UIImageView(image: calendarDayImage!)
calendarDayImageView.frame = CGRect(x: 25 , y: 350, width: 50, height: 50)
self.view.addSubview(calendarDayImageView)
case 8:
let calendarDay = "Calendar 8-50.png"
let calendarDayImage = UIImage(named: calendarDay)
let calendarDayImageView = UIImageView(image: calendarDayImage!)
calendarDayImageView.frame = CGRect(x: 25 , y: 350, width: 50, height: 50)
self.view.addSubview(calendarDayImageView)
case 11:
let calendarDay = "Calendar 11-50.png"
let calendarDayImage = UIImage(named: calendarDay)
let calendarDayImageView = UIImageView(image: calendarDayImage!)
calendarDayImageView.frame = CGRect(x: 25 , y: 350, width: 50, height: 50)
self.view.addSubview(calendarDayImageView)
case 12:
blahh blahh blahh all the way down
case 31:
let calendarDay = "Calendar 30-50.png"
let calendarDayImage = UIImage(named: calendarDay)
let calendarDayImageView = UIImageView(image: calendarDayImage!)
calendarDayImageView.frame = CGRect(x: 25 , y: 350, width: 50, height: 50)
self.view.addSubview(calendarDayImageView)
default:
println("Default")
break
}
}
First off: why are you actually switching? As far as I can tell the code can be reduced to
func dayCalendar() {
if day < 1 || day > 31 {
return
}
let calendarDay = "Calendar \(day)-50.png"
let calendarDayImage = UIImage(named: calendarDay)
let calendarDayImageView = UIImageView(image: calendarDayImage!)
calendarDayImageView.frame = CGRect(x: 25 , y: 350, width: 50, height: 50)
self.view.addSubview(calendarDayImageView)
}
You might want to move the call to viewWillAppear.
To fix the overlaying of the detail closures you have two options:
remove the added calendarDayImageView every time you display a new day.
or (much better)
reuse the already added image view if it is already present (might want to create a lazy variable) and just change the image it is displaying.
The later one you can achieve in the following way:
// add this variable to your class
lazy var calendarDayImageView: UIImageView = {
[unowned self] in
let imageV = UIImageView(frame: CGRect(x: 25 , y: 350, width: 50, height: 50))
self.view.addSubview(imageV)
return imageV
}()
// change the dayCalender to make use of the newly added variable
func dayCalendar() {
if day < 1 || day > 31 {
return
}
let calendarDay = "Calendar \(day)-50.png"
let calendarDayImage = UIImage(named: calendarDay)
calendarDayImageView.image = calendarDayImage
}
Alternatively you can just move the initialization of the view inside viewDidLoad and have dayCalender again only set the actual image - but i like lazy vars ;)

Resources