Calculate the size of text for draw in context(Core Graphics)? - swift

I need to know the size of my text that I am going to draw in context. How we can find the size of created text inside context?
What I should put for sizeOfText?
this is my code:
let text = "Some test"
let locationOfText: CGPoint = .zero
let fontOFText: CGFloat = 30
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.alignment = .left
let attrib: [NSAttributedString.Key: Any] = [
.font: UIFont.boldSystemFont(ofSize: fontOFText),
.paragraphStyle: paragraphStyle]
let sizeOfComponent = labelSimulator(withThisFont: UIFont.boldSystemFont(ofSize: fontOFText), withThisText: text)
let sizeOfText = CGSize(width: sizeOfComponent.width, height: sizeOfComponent.height)
text.draw(in: CGRect(origin: locationOfText, size: sizeOfText), withAttributes: Attrib)
This part is my way to find the Size of text, do you know better way, hit me!
//█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █
func labelSimulator(withThisFont: UIFont, withThisText: String) -> CGSize
{
//▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲
let simulator = UILabel()
simulator.font = withThisFont
simulator.text = withThisText
//▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼
//▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲
return simulator.sizeThatFits(CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude))
//▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼
}
//█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █

I have already shown to you how to calculate the height of your text on your last question. You just need to add a margin to it and move on. The width you choose will determine the final height:
let pdfFormat = UIGraphicsPDFRendererFormat()
let metaData: [CFString : Any] = [
kCGPDFContextTitle: "Hello, World!",
kCGPDFContextAuthor: "Omid",
kCGPDFContextCreator: "PDF Creator"]
pdfFormat.documentInfo = metaData as [String: Any]
// your PDF page size
let size: CGSize = .init(width: 8.5 * 72.0, height: 11 * 72.0)
let bounds: CGRect = .init(origin: .zero, size: size)
let pdfRenderer = UIGraphicsPDFRenderer(bounds: bounds, format: pdfFormat)
let documentDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
let data = pdfRenderer.pdfData { (context) in
context.beginPage()
let text = "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum."
let fontOFText: CGFloat = 30
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.alignment = .left
let attrib: [NSAttributedString.Key: Any] = [
.font: UIFont.boldSystemFont(ofSize: fontOFText),
.paragraphStyle: paragraphStyle]
let margin: CGFloat = 20
let attributedText = NSAttributedString(string: text, attributes: attrib)
let textCanvas = bounds.insetBy(dx: margin, dy: 0).size
let textSize = attributedText.boundingRect(with: textCanvas,
options: [.usesFontLeading, .usesLineFragmentOrigin],
context: nil).size
let rect = CGRect(origin: .init(x: margin/2, y: 0), size: textSize)
text.draw(in: rect, withAttributes: attrib)
let nextTextYPosition = rect.origin.y + rect.size.height
print("nextTextYPosition", nextTextYPosition)
"NEXT TEXT".draw(in: .init(origin: .init(x: margin/2, y: nextTextYPosition), size: CGSize.init(width: 200, height: 200)), withAttributes: attrib)
}
let fileURL = documentDirectory.appendingPathComponent("test.pdf")
try data.write(to: fileURL)

For the specific situation you've described, the size you generally want is CGSize(width: .greatestFiniteMagnitude, height: .greatestFiniteMagnitude). Depending on the context you're drawing into, you may want to constrain the height. If you want it to wrap, then you can constrain the width.
The in: parameter here is a constraining box. It's the largest box allowed. It doesn't have to tightly match the text.
If you need the actual size (in order to put an outline around it for example), you can compute that using .boundingRect(with:options:attributes:context:).
(Noting your edit, you seem to have the right idea. You just don't need a label. You can ask the string directly.)
For example, based on your code, there's no need for labelSimulator:
let sizeOfText = CGSize(width: CGFloat.greatestFiniteMagnitude, height: .greatestFiniteMagnitude)
text.draw(in: CGRect(origin: locationOfText, size: sizeOfText), withAttributes: attrib)

Related

redraw a custom UIVIEW when device orientation changed SWIFT 5

I wrote the code down for new UIVIEW but after device rotation the draw of UIVIEW do not get cleaned up for new situation! And stay visible for user! look at picture. how we can fix the problem?
import UIKit
class ViewController: UIViewController
{
override func viewDidLoad()
{
super.viewDidLoad()
}
override func viewDidLayoutSubviews()
{
var drawView = UIView()
let drawViewOffsetFromTop = max( 25 , Int( view.safeAreaInsets.top ) )
let drawViewOffsetFromBottom = max( 25 , Int( view.safeAreaInsets.bottom ) )
let drawViewWidth = Int( view.frame.size.width ) - 2*25
let drawViewHeight = Int( view.frame.size.height) - drawViewOffsetFromTop - drawViewOffsetFromBottom
drawView = UIView(frame: CGRect(x: 25 , y: drawViewOffsetFromTop, width: drawViewWidth , height: drawViewHeight ))
drawView.backgroundColor = UIColor.gray
drawView.layer.cornerRadius = 15
view.addSubview(drawView)
}
}
You can use Autolayout to make things simple.
Remove override viewDidLayoutSubviews completely.
Use the following in viewDidLoad
override func viewDidLoad()
{
super.viewDidLoad()
var drawView = UIView()
//Setup Constraints for `drawView`
drawView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
drawView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 25),
drawView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -25),
drawView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 15),
drawView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -15)
])
}
Edit: Solution without Autolayout
The issue is that your drawView is added multiple times in the view hierarchy! You can just add it once and only change the frame.
class ViewController: UIViewController {
var drawView = UIView()
override func viewDidLoad()
{
super.viewDidLoad()
view.addSubview(drawView)
drawView.backgroundColor = UIColor.gray
drawView.layer.cornerRadius = 15
}
override func viewDidLayoutSubviews()
{
let drawViewOffsetFromTop = max( 25 , Int( view.safeAreaInsets.top ) )
let drawViewOffsetFromBottom = max( 25 , Int( view.safeAreaInsets.bottom ) )
let drawViewWidth = Int( view.frame.size.width ) - 2*25
let drawViewHeight = Int( view.frame.size.height) - drawViewOffsetFromTop - drawViewOffsetFromBottom
drawView.frame = CGRect(x: 25 , y: drawViewOffsetFromTop, width: drawViewWidth , height: drawViewHeight )
}
}
First of all, you should not put creating UIView code in viewDidLayoutSubviews, create and add it in viewDidLoad instead. You can put view frame update code in viewDidLayoutSubviews.
The problem is you are adding drawView multiple times without removing existing ones. In addition you are initializing drawView multiple times in different lines unnecessarily.
To fix the problem, create drawView in your controller, add it to superview in viewDidLoad and edit your viewDidLayoutSubviews just like below:
class ViewController: UIViewController {
private var drawView : UIView = UIView()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(drawView)
}
override func viewDidLayoutSubviews() {
let drawViewOffsetFromTop = max(25 , Int(view.safeAreaInsets.top))
let drawViewOffsetFromBottom = max(25 , Int(view.safeAreaInsets.bottom))
let drawViewWidth = Int(view.frame.size.width ) - 2*25
let drawViewHeight = Int(view.frame.size.height) - drawViewOffsetFromTop - drawViewOffsetFromBottom
drawView.frame = CGRect(x: 25, y: drawViewOffsetFromTop, width: drawViewWidth, height: drawViewHeight)
drawView.backgroundColor = UIColor.gray
drawView.layer.cornerRadius = 15
}
}
//▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲
var drawView = UIView()
//▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼
//█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █
override func viewDidLoad()
{
super.viewDidLoad()
view.backgroundColor = UIColor.gray
}
//█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █
//█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █
override func viewDidLayoutSubviews()
{
//▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲
let backView = UIView(frame: CGRect(x: 0 , y: 0, width: view.frame.size.width , height: view.frame.size.height ))
backView.backgroundColor = UIColor.gray
view.addSubview(backView)
//▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼
//▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲
let drawViewOffsetFromTop = max( 25 , Int( view.safeAreaInsets.top ) )
let drawViewOffsetFromBottom = max( 25 , Int( view.safeAreaInsets.bottom ) )
let drawViewOffsetFromRight = max( 25 , Int( view.safeAreaInsets.right ) )
let drawViewOffsetFromLeft = max( 25 , Int( view.safeAreaInsets.left ) )
//▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼
//▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲
let drawViewWidth = Int( view.frame.size.width ) - drawViewOffsetFromRight - drawViewOffsetFromLeft
let drawViewHeight = Int( view.frame.size.height) - drawViewOffsetFromTop - drawViewOffsetFromBottom
//▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼
//▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲
drawView = UIView(frame: CGRect(x: drawViewOffsetFromRight , y: drawViewOffsetFromTop, width: drawViewWidth , height: drawViewHeight ))
//▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼
//▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲
drawView.backgroundColor = UIColor.systemTeal
drawView.layer.cornerRadius = 12
drawView.layer.borderWidth = 5
drawView.layer.borderColor = UIColor.black.cgColor
view.addSubview(drawView)
//▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼
}
//█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █

How to create random string with multiple formats?

I need to create a random string with format which can convert any string(including parenthesis() to another color on swift: for example:
Hey (Hey) : First part 'Hey' is fine, but I want to change : (Hey) to a different color
same goes if I choose another string
Hi (What's Up) ....
And tried the following
let label = UILabel(frame: CGRect(origin: .zero, size: CGSize(width: 200, height: 50)))
let color = UIColor(white: 0.2, alpha: 1)
let attributedTextCustom = NSMutableAttributedString(string: "(\(String())", attributes: [.font: UIFont(name:"AvenirNext-Medium", size: 16)!, .foregroundColor: color]))
attributedTextCustom.append(NSAttributedString(string: " (\(String())", attributes: [.font: UIFont(name: "AvenirNext-Regular", size: 12)!, .foregroundColor: UIColor.lightGray]))
label.attributedText = attributedTextCustom
Something like this is the behavior I am looking for (just for demostration...):
You can use a regex "\\((.*?)\\)" to find the range of the word between the parentheses and add the color attribute to a NSMutableAttributedString:
let label = UILabel(frame: CGRect(origin: .zero, size: CGSize(width: 200, height: 50)))
let sentence = "Hello (Playground)"
let mutableAttr = NSMutableAttributedString(string: sentence, attributes: [.font: UIFont(name:"AvenirNext-Medium", size: 16)!, .foregroundColor: UIColor.black])
if let range = sentence.range(of: "\\((.*?)\\)", options: .regularExpression) {
let color = UIColor(white: 0.2, alpha: 1)
let attributes: [NSAttributedString.Key: Any] = [.font: UIFont(name:"AvenirNext-Medium", size: 16)!, .foregroundColor: color]
mutableAttr.addAttributes(attributes, range: NSRange(range, in: sentence))
label.attributedText = mutableAttr
}

UISegmentControl underline not on top in some cases

I'm trying to create a segment control with an underline for the selected segment. I've found a few code snippets to do this but they seem to exhibit the same behavior where the underline is beneath the UISegment of the UISegment Control. In this particular case, I'm using a UITableView custom cell with a UISegmentControl that has constraints to the top, left, bottom and right corners of the cell. Only the 2nd segment shows up properly. Here is what the debug view hierarchy looks like:
Here is the code that I'm using:
extension UISegmentedControl{
func removeBorder(){
let backgroundImage = UIImage.getColoredRectImageWith(color: UIColor.white.cgColor, andSize: self.bounds.size)
self.setBackgroundImage(backgroundImage, for: .normal, barMetrics: .default)
self.setBackgroundImage(backgroundImage, for: .selected, barMetrics: .default)
self.setBackgroundImage(backgroundImage, for: .highlighted, barMetrics: .default)
let deviderImage = UIImage.getColoredRectImageWith(color: UIColor.white.cgColor, andSize: CGSize(width: 1.0, height: self.bounds.size.height))
self.setDividerImage(deviderImage, forLeftSegmentState: .selected, rightSegmentState: .normal, barMetrics: .default)
self.setTitleTextAttributes([NSForegroundColorAttributeName: UIColor.gray], for: .normal)
self.setTitleTextAttributes([NSForegroundColorAttributeName: UIColor(red: 67/255, green: 129/255, blue: 244/255, alpha: 1.0)], for: .selected)
}
func addUnderlineForSelectedSegment(){
removeBorder()
let underlineWidth: CGFloat = self.bounds.size.width / CGFloat(self.numberOfSegments)
let underlineHeight: CGFloat = 2.0
let underlineXPosition = CGFloat(selectedSegmentIndex * Int(underlineWidth))
let underLineYPosition = self.bounds.size.height - 5.0
let underlineFrame = CGRect(x: underlineXPosition, y: underLineYPosition, width: underlineWidth, height: underlineHeight)
let underline = UIView(frame: underlineFrame)
underline.backgroundColor = UIColor(red: 67/255, green: 129/255, blue: 244/255, alpha: 1.0)
underline.tag = 1
self.addSubview(underline)
}
func changeUnderlinePosition(){
guard let underline = self.viewWithTag(1) else {return}
let underlineFinalXPosition = (self.bounds.width / CGFloat(self.numberOfSegments)) * CGFloat(selectedSegmentIndex)
UIView.animate(withDuration: 0.1, animations: {
underline.frame.origin.x = underlineFinalXPosition
})
}
}
Anyone have any ideas what is causing this?
This feels a bit hacky but I changed the following line:
self.addSubview(underline)
To the following:
for each in self.subviews {
each.addSubview(underline)
Now, it works properly. Even in the debug view hierarchy, I only see one underline UIView. Strange.
Still interested in any other answers that might be a better solution.

Swift UIActivityViewController Email subject line inherits global UINavigationBar font change

I change the size and weight of the UINavigationBar font with the following:
UINavigationBar.appearance().titleTextAttributes = [NSAttributedStringKey.foregroundColor: UIColor.black]
UINavigationBar.appearance().titleTextAttributes = [ NSAttributedStringKey.font: UIFont.systemFont(ofSize: 28, weight: .heavy)]
When I present the UIActivityViewController and select Mail, the subject line in the title (not the email) inherits the font change. In order to change this, I change the UINavigationBar back to something smaller.
While this works, and the subject line in the title is smaller, and now readable, when the UIActivityViewController returns, naturally the UINavigationBar font is still set to the new size and weight. I tried the completion as per below, but the font doesn't resize.
#objc func showActivityViewController() {
UINavigationBar.appearance().titleTextAttributes = [NSAttributedStringKey.foregroundColor: UIColor.black]
UINavigationBar.appearance().titleTextAttributes = [ NSAttributedStringKey.font: UIFont.systemFont(ofSize: 17, weight: .thin)]
present(activityViewController, animated: true, completion: {
UINavigationBar.appearance().titleTextAttributes = [NSAttributedStringKey.foregroundColor: UIColor.black]
UINavigationBar.appearance().titleTextAttributes = [ NSAttributedStringKey.font: UIFont.systemFont(ofSize: 28, weight: .heavy)]
})
}
I change the font color of a UIButton in a similar way. I found that using the main thread helped. Try this and see if it works:
present(activityViewController, animated: true, completion: {
DispatchQueue.main.async {
UINavigationBar.appearance().titleTextAttributes = [NSAttributedStringKey.foregroundColor: UIColor.black]
UINavigationBar.appearance().titleTextAttributes = [NSAttributedStringKey.font: UIFont.systemFont(ofSize: 28, weight: .heavy)]
}
})

whats the seed for spacefiller pattern in conway's game of life

Can anybody tell me what is the seed for spacefiller pattern in Conway's game of life ? I am planning to include some interesting patterns for user in my game.
Thanks much,
From Golly, a really nice Sourceforge Game-of-Life project with a huge number of seeds:
███ ███
█ █ █ █
████ █ █ ████
█ █ █ █ █ █
█ █ █ █ █ █
█ █ ██ █ █ ██ █ █
█ █ ███ ███ █ █
█ █ █ █ █ █
█ █ ███████ █ █
█ █ ██ █ ██ █ █ ██ █ ██ █ █
█ █ ██ ███████████ ██ █ █
█ █ ██ ██ █ █
████ ███████████████████ ████
█ █ █ █
███████████
█ █
█████████
█
███ ███
█ █
███ ███
███ ███
█ ██ ██ █
███ ███
█ █
I believe you are referring to the glider pattern?
One of the frames looks like;
010
001
111
Edit:
Try
http://www.argentum.freeserve.co.uk/lex_s.htm#spacefiller
I believe missKK is looking for a Conway's Game of Life pattern that will, if placed in empty space, fill the space with an agar. Although new Life patterns are discovered all the time, as of today the smallest known pattern is Tim Coe's "Max", a 187-cell pattern which fits in a 27x27 bounding box:
#N Max
#O Tim Coe
#C A spacefiller that fills space with zebra stripes.
#C www.conwaylife.com/wiki/index.php?title=Max
x = 27, y = 27, rule = s23/b3
18bo8b$17b3o7b$12b3o4b2o6b$11bo2b3o2bob2o4b$10bo3bobo2bobo5b$10bo4bobo
bobob2o2b$12bo4bobo3b2o2b$4o5bobo4bo3bob3o2b$o3b2obob3ob2o9b2ob$o5b2o
5bo13b$bo2b2obo2bo2bob2o10b$7bobobobobobo5b4o$bo2b2obo2bo2bo2b2obob2o
3bo$o5b2o3bobobo3b2o5bo$o3b2obob2o2bo2bo2bob2o2bob$4o5bobobobobobo7b$
10b2obo2bo2bob2o2bob$13bo5b2o5bo$b2o9b2ob3obob2o3bo$2b3obo3bo4bobo5b4o
$2b2o3bobo4bo12b$2b2obobobobo4bo10b$5bobo2bobo3bo10b$4b2obo2b3o2bo11b$
6b2o4b3o12b$7b3o17b$8bo!
The Lifewiki website has more information on this pattern and other known spacefillers.