Draw circle behind particular characters of UILabel - ios

I am trying to draw circle behind particular parts of UILabel characters without having to use several labels or recompute frames.
I know that I can insert an image into UILabel using attributed text but I want the circle to stand behind the numbers (from 1 to 9). I don't want to store images from 1 to 9.
I also cannot find any pod achieving this.
let fullText = NSMutableAttributedString()
let imageAttachment = NSTextAttachment()
imageAttachment.image = UIImage(named: "\(number)_\(color == .yellow ? "yellow" : "gray")")
let imageString = NSAttributedString(attachment: imageAttachment)
let endString = NSAttributedString("nouveaux")
fullString.append(imageString)
fullString.append(endString)
mainLabel.attributedText = fullString
EDIT
I want to draw circle behind each digit of a string.
I would like it to be a generic tool if possible, some kind of parser that returns an attributed string if possible, a unique UIView if not.
Another use case is "4 messages | chat 2" (4 being yellow, 2 being gray)

I don't understand why you don't just use a utility function that draws the digit-in-a-circle in real time. That way, you can have any digit font, circle size, digit color, and circle color that you like. For example:
func circleAroundDigit(_ num:Int, circleColor:UIColor,
digitColor:UIColor, diameter:CGFloat,
font:UIFont) -> UIImage {
precondition((0...9).contains(num), "digit is not a digit")
let p = NSMutableParagraphStyle()
p.alignment = .center
let s = NSAttributedString(string: String(num), attributes:
[.font:font, .foregroundColor:digitColor, .paragraphStyle:p])
let r = UIGraphicsImageRenderer(size: CGSize(width:diameter, height:diameter))
return r.image {con in
circleColor.setFill()
con.cgContext.fillEllipse(in:
CGRect(x: 0, y: 0, width: diameter, height: diameter))
s.draw(in: CGRect(x: 0, y: diameter / 2 - font.lineHeight / 2,
width: diameter, height: diameter))
}
}
Here's how to use it:
let im = circleAroundDigit(3, circleColor: .yellow,
digitColor: .white, diameter: 30, font:UIFont(name: "GillSans", size: 20)!)
Here's the resulting image; note, though, that every aspect of this can be tweaked just by calling the function differently:
Okay, so now you've got an image. You say you already know how to insert an image inline into an attributed string and display that as part of the label, so I won't explain how to do that:

This is pretty straightforward using a UIStackView, setting the corner radius of the coloured view's layer…
result…
or with 2 stack views in an outer stack view≥
result…

Related

Swift: Display (LaTeX) math expressions inline

I would like to display math terms inside a text, in particular in an inline mode, i.e. inside a sentence.
Using LaTeX, this would for example look like:
"Given a right triangle having catheti of length \(a\) resp. \(b\) and a hypotenuse of length \(c\), we have
\[a^2 + b^2 = c^2.\]
This fact is known as the Pythagorean theorem."
Does anybody know how this can be achieved in Swift?
(I know that this example may be achieved in Swift without LaTeX-like tools. However, the expressions in my mind are in fact more complex than in this example, I do need the power of LaTeX.)
The optimal way would be a UITextView-like class which recognizes the math delimiters \(,\) resp. \[,\], recognizes LaTeX code inside these delimiters, and formats the text accordingly.
In the Khan Academy app, this problem seems to be solved as the screenshots in the Apple App Store/Google Play Store show inline (LaTeX) math.
I’ve found the package iosMath which provides a UILabel-like class MTMathUILabel. As this class can display solely formulas, this seems to be not good enough for my purpose, except if there was a method which takes a LaTeX source text such as in the example above, formats expressions such as \(a\) into tiny MTMathUILabels and sets these labels between the other text components. As I am new to Swift, I do not know whether and how this can be achieved. Moreover, this seems to be very difficult from a typographical point of view as there will surely occur difficulties with line breaks. And there might occur performance issues if there are a large number of such labels on the screen at the same time?
It is possible to achieve what I want using a WKWebView and MathJax or KaTeX, which is also a hack, of course. This leads to other difficulties, e.g. if one wants to set several of these WKWebViews on a screen, e.g. inside UITableViewCells.
Using iosMath, my solution on how to get a UILabel to have inline LaTeX is to include LATEX and ENDLATEX markers with no space. I replaced all ranges with an image of the MTMathUILabel, going from last range to first range so the positions don't get screwed up (This solution allows for multiple markers). The image returned from my function is flipped so i used .downMirrored orientation, and i sized it to fit my text, so you might need to fix the numbers a little for the flip scale of 2.5 and the y value for the attachment.bounds.
import UIKit
import iosMath
let question = UILabel()
let currentQuestion = "Given a right triangle having catheti of length LATEX(a)ENDLATEX resp. LATEX(b)ENDLATEX and a hypotenuse of length LATEX(c)ENDLATEX, we have LATEX[a^2 + b^2 = c^2]ENDLATEX. This fact is known as the Pythagorean theorem."
question.text = currentQuestion
if (question.text?.contains("LATEX"))! {
let tempString = question.text!
let tempMutableString = NSMutableAttributedString(string: tempString)
let pattern = NSRegularExpression.escapedPattern(for: "LATEX")
let regex = try? NSRegularExpression(pattern: pattern, options: [])
if let matches = regex?.matches(in: tempString, options: [], range: NSRange(location: 0, length: tempString.count)) {
var i = 0
while i < matches.count {
let range1 = matches.reversed()[i+1].range
let range2 = matches.reversed()[i].range
let finalDistance = range2.location - range1.location + 5
let finalRange = NSRange(location: range1.location, length: finalDistance)
let startIndex = String.Index(utf16Offset: range1.location + 5, in: tempString)
let endIndex = String.Index(utf16Offset: range2.location - 3, in: tempString)
let substring = String(tempString[startIndex..<endIndex])
var image = UIImage()
image = imageWithLabel(string: substring)
let flip = UIImage(cgImage: image.cgImage!, scale: 2.5, orientation: .downMirrored)
let attachment = NSTextAttachment()
attachment.image = flip
attachment.bounds = CGRect(x: 0, y: -flip.size.height/2 + 10, width: flip.size.width, height: flip.size.height)
let replacement = NSAttributedString(attachment: attachment)
tempMutableString.replaceCharacters(in: finalRange, with: replacement)
question.attributedText = tempMutableString
i += 2
}
}
}
func imageWithLabel(string: String) -> UIImage {
let label = MTMathUILabel()
label.latex = string
label.sizeToFit()
UIGraphicsBeginImageContextWithOptions(label.bounds.size, false, 0)
defer { UIGraphicsEndImageContext() }
label.layer.render(in: UIGraphicsGetCurrentContext()!)
return UIGraphicsGetImageFromCurrentImageContext() ?? UIImage()
}

Swift 3 - NSString.draw(in: rect, withAttributes:) -- Text not being drawn at expected point

Teeing off of this Stackoverflow post, which was very helpful, I've been able to successfully draw text onto a full-screen image (I'm tagging the image with pre-canned, short strings, e.g., "Trash"). However, the text isn't appearing where I want, which is centered at the exact point the user has tapped. Here's my code, based on some code from the above post but updated for Swift3 --
func addTextToImage(text: NSString, inImage: UIImage, atPoint:CGPoint) -> UIImage{
// Setup the font specific variables
let textColor: UIColor = UIColor.red
let textFont: UIFont = UIFont(name: "Helvetica Bold", size: 80)!
//Setups up the font attributes that will be later used to dictate how the text should be drawn
let textFontAttributes = [
NSFontAttributeName: textFont,
NSForegroundColorAttributeName: textColor,
]
// Create bitmap based graphics context
UIGraphicsBeginImageContextWithOptions(inImage.size, false, 0.0)
//Put the image into a rectangle as large as the original image.
inImage.draw(in: CGRect(x:0, y:0, width:inImage.size.width, height: inImage.size.height))
// Create the rectangle where the text will be written
let rect: CGRect = CGRect(x:atPoint.x, y:atPoint.y, width:inImage.size.width, height: inImage.size.height)
// Draft the text in the rectangle
text.draw(in: rect, withAttributes: textFontAttributes)
// Get the image from the graphics context
let newImag = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return newImag!
}
In the above, atPoint is the location of the user's tap. This is where I want the text to be drawn. However, the text is always written toward the upper left corner of the image. For example, in the attached image, I have tapped half way down the waterfall as that is where I want the text string "Trash" to be written. But instead, you can see that it is written way up in the left-hand corner. I've tried a bunch of stuff but can't get a solution. I appreciate any help.
enter image description here
TrashShouldBeInMiddleOfWaterfallButIsNot
How are you setting atPoint? If you are using the same coordinate space as the screen, that won't work... which is what I suspect is happening.
Suppose your image is 1000 x 2000, and you are showing it in a UIImageView that is 100 x 200. If you tap at x: 50 y: 100 in the view (at the center), and then send that point to your function, it will draw the text at x: 50 y: 100 of the image -- which will be in the upper-left corner, instead of in the center.
So, you need to convert your point from the Image View size to the actual image size.. either before you call your function, or by modifying your function to handle it.
An example (not necessarily the best way to do it):
// assume:
// View Size is 100 x 200
// Image Size is 1000 x 2000
// tapPoint is CGPoint(x: 50, y: 100)
let xFactor = image.size.width / imageView.frame.size.width
// xFactor now equals 10
let yFactor = image.size.height / imageView.frame.size.height
// yFactor now equals 10
let convertedPoint = CGPoint(x: tapPoint.x * xFactor, y: tapPoint.y * yFactor)
convertedPoint now equals CGPoint(x: 500, y: 1000), and you can send that as the atPoint value in your call to addTextToImage.

label creation not positioned in the dwg

I have a function that gets called to create labels and position them to simulate dimensions in a drawing (like a cad front view); however they all get positioned in the top left even though the x and y coordinates are being sent correctly via the call function. I've hit a wall on this one and would appreciate any help.see screenshot I took and you'll notice that all the labels are scrunched up in the top left corner
All the dwg gets added to the CGContext
Here's my function:
public func drawText(ctx: CGContext,
txtCol:UIColor,
text:String,
posX:Double,
posY:Double,
rotation:Double,
fill:Bool) -> CGContext {
let label = UILabel()
label.textAlignment = .center
label.text = text
label.textColor = txtCol
label.transform = CGAffineTransform(rotationAngle: CGFloat.pi / 2)
if fill {
label.backgroundColor = UIColor.yellow
}
label.draw(CGRect(x: CGFloat(posX), y: CGFloat(posY), width: 100, height: 30))
label.layer.render(in: ctx)
return ctx
}
As renderInContext: documentation states : "Renders in the coordinate space of the layer." This means the frame origin of your layer/view is ignored.
You might want to use CGContextTranslateCTM before calling label.layer.render(in: ctx). Remember to save and restore the CTM on each call using CGContextSaveGState and CGContextRestoreGState.
Also, I doubt the need to call label.draw(CGRect(x: CGFloat(posX), y: CGFloat(posY), width: 100, height: 30)). Since it should not be call manually.

Swift Change UIView Image

I am trying to make a basic game for iOS10 using swift 3 and scenekit. In one part of my games code I have a function that adds fishes to the screen, and gives each one a certain tag so i can find them later:
let fish = UIImageView(frame: CGRect(x: 0, y: 0, width: CGFloat(fishsize.0), height: CGFloat(fishsize.1)))
fish.contentMode = .scaleAspectFit
fish.center = CGPoint(x: CGFloat(fishsize.0) * -0.6, y: pos)
fish.animationImages = fImg(Fishes[j].size, front: Fishes[j].front)
fish.animationDuration = 0.7
fish.startAnimating()
fish.tag = j + 200
self.view.insertSubview(fish, belowSubview: big1)
What I would like is to be able to, at a certain point, recall the fish and
Change the images shown, and
Stop the animation.
Is this possible? I've been trying it with var fish = view.viewWithTag(killer+200)! but from this I can't seem to change any image properties of the new variable fish.
Any help would be much appreciated.
Tom
Try to cast the UIView to UIImageView like this.
if let fish = view.viewWithTag(killer+200) as? UIImageView {
//Perform your action on imageView object
}

How to remove labels from after drawing with drawrect()

I am pretty new to drawing programmatically. I've managed to draw a line graph, complete with points, lines, auto-scaling axes, and axis labels. In other screens, I can change characteristics of the graph and when I return to the screen, I refresh it with setNeedsDisplay() in the viewWillAppear function of the containing viewController. The lines are redrawn perfectly when I do this.
The new data that is added in other screens may require rescaling the axes. The problem is that when the graph is redrawn, the number labels on the axes are just added to the graph, without removing the old ones, meaning that some labels may be overwritten, while some old ones just remain there next to the new ones.
I think I see why this happens, in that I am creating a label and adding a subview, but not removing it. I guess I figured that since the lines are erased and redrawn, the labels would be, too. How do I cleanly relabel my axes? Is there a better way to do this? My function for creating the labels is listed below. This function is called by drawRect()
func createXAxisLabels(interval: Float, numIntervals: Int) {
let xstart: CGFloat = marginLeft
let yval: CGFloat = marginTop + graphHeight + 10 // 10 pts below the x-axis
var xLabelVals : [Float] = [0]
var xLabelLocs : [CGFloat] = [] // gives the locations for each label
for i in 0...numIntervals {
xLabelLocs.append(xstart + CGFloat(i) * graphWidth/CGFloat(numIntervals))
xLabelVals.append(Float(i) * interval)
}
if interval >= 60.0 {
xUnits = "Minutes"
xUnitDivider = 60
}
for i in 0...numIntervals {
let label = UILabel(frame: CGRectMake(0, 0, 50.0, 16.0))
label.center = CGPoint(x: xLabelLocs[i], y: yval)
label.textAlignment = NSTextAlignment.Center
if interval < 1.0 {
label.text = "\(Float(i) * interval)"
} else {
label.text = "\(i * Int(interval/xUnitDivider))"
}
label.font = UIFont.systemFontOfSize(14)
label.textColor = graphStructureColor
self.addSubview(label)
}
}
drawRect should just draw the rectangle, not change the view hierarchy. It can be called repeatedly. Those other labels are other views and do their own drawing.
You have a couple of options
Don't use labels -- instead just draw the text onto the rect.
-or-
Add/remove the labels in the view controller.
EDIT (from the comments): The OP provided their solution
I just replaced my code inside the for loop with:
let str : NSString = "\(xLabelVals[i])"
let paraAttrib = NSMutableParagraphStyle()
paraAttrib.alignment = NSTextAlignment.Center
let attributes = [
NSFontAttributeName: UIFont.systemFontOfSize(14),
NSForegroundColorAttributeName: UIColor.whiteColor(),
NSParagraphStyleAttributeName: paraAttrib]
let xLoc : CGFloat = CGFloat(xLabelLocs[i] - 25)
let yLoc : CGFloat = yval - 8.0
str.drawInRect(CGRectMake(xLoc, yLoc, 50, 16), withAttributes: attributes)

Resources