How can I center an image on a textView who is attached to a NSTextAttachment type? - swift

I have a textView (programmatically) and I inserted an image appended to a NSTextAttachment and want it to be aligned to the center of the text view... but I haven't found any solutions yet...I was wondering if that task is possible to be done via code only...
let image = ResizeImage(image: UIImage(named: "logo")!, targetSize: size)
let image1Attachment = NSTextAttachment()
image1Attachment.image = image
let image1Attachments = NSAttributedString(attachment: image1Attachment)
My best approach to the solution on trying to achieve this was this code :
let image = ResizeImage(image: UIImage(named: "telepaint1")!,
targetSize: size)
//let textAttachment = NSTextAttachment()
let textAttachment = NSTextAttachment()
//let imageStyle = NSMutableParagraphStyle()
let imageStyle = NSMutableParagraphStyle()
//imageStyle.alignment = .center
imageStyle.alignment = .center
//textAttachment.image = image
textAttachment.image = image
let imageText = NSAttributedString(attachment:
textAttachment).mutableCopy() as! NSMutableAttributedString
//attempt to center image...
let attributedStringToAppend: NSMutableAttributedString =
NSMutableAttributedString(attributedString:
NSAttributedString(string: "\n\n"))
//attributedStringToAppend.addAttributes(attr, range: range)
let combination2: NSMutableAttributedString =
NSMutableAttributedString(attributedString:
NSAttributedString(string: "\n\n"))
combination2.append(lineBreak)
// combination2.addAttribute(attr, range: NSRange(location: 0,
length: length))
combination2.append(imageText)
//this text will display the "Walktrough" text on image
combination2.append(attributedText)
tv.attributedText = combination2

Answer(worked for me!):
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.alignment = .center
attributedText.addAttribute(NSAttributedString.Key.paragraphStyle, value: paragraphStyle, range: NSRange(location: 0, length: length))
let size = CGSize(width: 100, height: 100)
//Here is an attempt to resize image and center it...
let image = ResizeImage(image: UIImage(named: "logo")!.withRenderingMode(.alwaysTemplate), targetSize: size)
let textAttachment = NSTextAttachment()
let imageStyle = NSMutableParagraphStyle()
imageStyle.alignment = .center
textAttachment.image = image
let imageText = NSAttributedString(attachment: textAttachment).mutableCopy() as! NSMutableAttributedString
let length2 = imageText.length
imageText.addAttribute(NSAttributedString.Key.paragraphStyle, value: imageStyle, range: NSRange(location: 0, length: length2))

Related

CIAttributedTextImageGenerator filter - text doesn't fit (NSAttributedString)

I use a Core Image filter CIAttributedTextImageGenerator to generate text as a CIImage. However, sometimes the text just doesn't fit into the resulted CIImage as you can see at the picture:
I tried to play with different key-values of NSAttributedString to make some padding around text but with no success:
func generateImageFromText(_ text: String, style: TextStyle) -> CIImage? {
let font = UIFont.init(name: style.fontName, size: style.fontSize)!
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.alignment = style.textAlignment
paragraphStyle.headIndent = 5.0
paragraphStyle.tailIndent = -5.0
paragraphStyle.firstLineHeadIndent = 0
let shadow = NSShadow()
if let shadowStyle = style.shadowStyle {
shadow.shadowColor = shadowStyle.color
shadow.shadowOffset = shadowStyle.offset
shadow.shadowBlurRadius = shadowStyle.blurRadius
}
var strokeColor = UIColor.clear
var strokeWidth: CGFloat = 0.0
if let strokeStyle = style.strokeStyle {
strokeColor = strokeStyle.color
strokeWidth = strokeStyle.width
}
let attributes: [NSAttributedString.Key: Any] = [
.baselineOffset: 50,
.font: font,
.foregroundColor: style.color,
.paragraphStyle: paragraphStyle,
.shadow: shadow,
.strokeColor: strokeColor,
.strokeWidth: strokeWidth
]
let attributedQuote = NSAttributedString(string: text, attributes: attributes)
let textGenerationFilter = CIFilter(name: "CIAttributedTextImageGenerator")!
textGenerationFilter.setValue(attributedQuote, forKey: "inputText")
textGenerationFilter.setValue(NSNumber(value: Double(1.0)), forKey: "inputScaleFactor")
guard let textImage = textGenerationFilter.outputImage else {
return nil
}
return textImage
}
Maybe there are some values of NSAttributedString that I miss which can help to fit in the text?

How to set a proper location for CIAttributedTextImageGenerator?

I use "CIAttributedTextImageGenerator" to generate a CIImage from text and then I overlay it on my edited image with "CISourceAtopCompositing" :
// Text to image
let font = UIFont.systemFont(ofSize: 78)
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.alignment = .center
paragraphStyle.firstLineHeadIndent = 5.0
let shadow = NSShadow()
shadow.shadowColor = UIColor.red
shadow.shadowBlurRadius = 5
let attributes: [NSAttributedString.Key: Any] = [
.font: font,
.foregroundColor: UIColor.blue,
.paragraphStyle: paragraphStyle,
.shadow: shadow
]
let attributedQuote = NSAttributedString(string: "General Kenobi", attributes: attributes)
let textGenerationFilter = CIFilter(name: "CIAttributedTextImageGenerator")!
textGenerationFilter.setValue(attributedQuote, forKey: "inputText")
textGenerationFilter.setValue(NSNumber(value: Double(inputSizeFactor)), forKey: "inputScaleFactor")
let textImage = textGenerationFilter.outputImage!.oriented(.right)
finalImage = textImage
.applyingFilter("CISourceAtopCompositing", parameters: [ kCIInputBackgroundImageKey: finalImage])
However, the text image is always in the right bottom corner of my edited image:
How can I set it a custom location? For example, if I want it to be in the bottom right corner, or in the center of the edited image?
You can apply transformations to the image before you composite it over the background:
let transformedText = textImage.transformed(by: CGAffineTransform(translationX: 200, y: 300)
I'm afraid you have to calculate the exact position for centering the image yourself. There's no built-in way to center one image ontop another.

Swift 4 - NSGraphicsContext.setCurrent() has no member 'setCurrent'

So I am trying to overlay text onto a PDF document using a similar method to this answer.
I use a function to set the current Graphics Context, like it says to in the documentation.
let pdfPage: CGPDFPage = pdf.page(at: 1)!
//var pageRect = pdfPage.getBoxRect(CGPDFBox.mediaBox)
//print(pageRect)
let doc: PDFDocument = PDFDocument(url: pdfURL!)!
let page: PDFPage = doc.page(at: 0)!
var mediaBox: CGRect = page.bounds(for: .mediaBox)
let context = CGContext(destinationURL as CFURL, mediaBox: &mediaBox, nil)
let graphicsContext = NSGraphicsContext(cgContext: context!, flipped: false)
NSGraphicsContext.setCurrent(graphicsContext)
context!.beginPDFPage(nil)
page.draw(with: .mediaBox, to: context!)
let style = NSMutableParagraphStyle()
style.alignment = .center
let richText = NSAttributedString(string: "Hello, world!", attributes: [
NSAttributedStringKey.font: NSFont.systemFont(ofSize: 64),
NSAttributedStringKey.foregroundColor: NSColor.red,
NSAttributedStringKey.paragraphStyle: style
])
let richTextBounds = richText.size()
let point = CGPoint(x: mediaBox.midX - richTextBounds.width / 2, y: mediaBox.midY - richTextBounds.height / 2)
context!.saveGState()
do {
context!.translateBy(x: point.x, y: point.y)
context!.rotate(by: .pi / 5)
richText.draw(at: .zero)
}
context!.restoreGState()
context!.endPDFPage()
NSGraphicsContext.setCurrent(nil)
context?.closePDF()
}
And the line:
NSGraphicsContext.setCurrent(graphicsContext)
Throws an error that says "Type 'NSGraphicsContext' has no member 'setCurrent'"
Anyone have any ideas on what's going on? Is there something I'm missing in terms of a framework?
You need to use the current class property.
NSGraphicsContext.current = graphicsContext

How to get the height of a UILabel in Swift?

I am a beginner in Swift and I am trying to get the height of a label.
The label has multiple lines of text. I want to know the total height it occupies on the screen.
Swift 4 with extension
extension UILabel{
public var requiredHeight: CGFloat {
let label = UILabel(frame: CGRect(x: 0, y: 0, width: frame.width, height: CGFloat.greatestFiniteMagnitude))
label.numberOfLines = 0
label.lineBreakMode = NSLineBreakMode.byWordWrapping
label.font = font
label.text = text
label.attributedText = attributedText
label.sizeToFit()
return label.frame.height
}
}
it's simple, just call
label.bounds.size.height
Updated for Swift 3
func estimatedHeightOfLabel(text: String) -> CGFloat {
let size = CGSize(width: view.frame.width - 16, height: 1000)
let options = NSStringDrawingOptions.usesFontLeading.union(.usesLineFragmentOrigin)
let attributes = [NSFontAttributeName: UIFont.systemFont(ofSize: 10)]
let rectangleHeight = String(text).boundingRect(with: size, options: options, attributes: attributes, context: nil).height
return rectangleHeight
}
override func viewDidLoad() {
super.viewDidLoad()
guard let labelText = label1.text else { return }
let height = estimatedHeightOfLabel(text: labelText)
print(height)
}
Swift 5 ioS 13.2 tested 100%, best solution when the UILabel numberOfLines = 0
Note, result is rounded. Just remove ceil() if you don't want it.
If you want to get height -> give storyboard width of UILabel
If you want to get width -> give storyboard height of UILabel
let stringValue = ""//your label text
let width:CGFloat = 0//storybord width of UILabel
let height:CGFloat = 0//storyboard height of UILabel
let font = UIFont(name: "HelveticaNeue-Bold", size: 18)//font type and size
func getLableHeightRuntime() -> CGFloat {
let constraintRect = CGSize(width: width, height: .greatestFiniteMagnitude)
let boundingBox = stringValue.boundingRect(with: constraintRect, options: .usesLineFragmentOrigin, attributes: [NSAttributedString.Key.font: font], context: nil)
return ceil(boundingBox.height)
}
func getLabelWidthRuntime() -> CGFloat {
let constraintRect = CGSize(width: .greatestFiniteMagnitude, height: height)
let boundingBox = stringValue.boundingRect(with: constraintRect, options: .usesLineFragmentOrigin, attributes: [NSAttributedString.Key.font: font], context: nil)
return ceil(boundingBox.width)
}
#iajmeri43's answer Updated for Swift 5
func estimatedLabelHeight(text: String, width: CGFloat, font: UIFont) -> CGFloat {
let size = CGSize(width: width, height: 1000)
let options = NSStringDrawingOptions.usesFontLeading.union(.usesLineFragmentOrigin)
let attributes = [NSAttributedString.Key.font: font]
let rectangleHeight = String(text).boundingRect(with: size, options: options, attributes: attributes, context: nil).height
return rectangleHeight
}
To use it:
// 1. get the text from the label
guard let theLabelsText = myLabel.text else { return }
// 2. get the width of the view the label is in for example a cell
// Here I'm just stating that the cell is the same exact width of whatever the collection's width is which is usually based on the width of the view that collectionView is in
let widthOfCell = self.collectionView.frame.width
// 3. get the font that your using for the label. For this example the label's font is UIFont.systemFont(ofSize: 17)
let theLabelsFont = UIFont.systemFont(ofSize: 17)
// 4. Plug the 3 values from above into the function
let totalLabelHeight = estimatedLabelHeight(text: theLabelsText, width: widthOfCell, font: theLabelsFont)
// 5. Print out the label's height with decimal values eg. 95.46875
print(totalLabelHeight)
// 6. as #slashburn suggested in the comments, use the ceil() function to round out the totalLabelHeight
let ceilHeight = ceil(totalLabelHeight)
// 7. Print out the ceilHeight rounded off eg. 95.0
print(ceilHeight)

CATextLayer is ignoring NSMutableParagraphStyle

I want to use NSMutableAttributedString to draw the text on the CATextLayer with the text. I try set attributes to the like font and fontcolor of the NSMutableAttributedString through the [attrString setAttributes:attributes range:range]; but all attributes in the NSMutableParagraphStyle are ignored.
let str = "I want to use NSMutableAttributedString to draw the text on the CATextLayer with the text. I try set attributes to the like font and fontcolor of the NSMutableAttributedString through the [attrString setAttributes:attributes range:range]; but all attributes in the NSMutableParagraphStyle are ignored."
let att = NSMutableAttributedString(string: str)
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.lineSpacing = 12
att.addAttribute(NSParagraphStyleAttributeName, value: paragraphStyle, range: NSMakeRange(0, str.characters.count))
let contentLayer = initTextLayer("", andFontSize: 11)
contentLayer.string = att
contentLayer.frame = CGRect(x: 30 * AUTOSIZESCALEX, y: CGRectGetMaxY(titleLayer.frame) + 15 * AUTOSIZESCALEY, width: SCREEN_WIDTH - 60 * AUTOSIZESCALEX, height: 200 * AUTOSIZESCALEY)
self.contentView.layer.addSublayer(contentLayer)
func initTextLayer(title: String, andFontSize size: CGFloat) -> CATextLayer{
let textLayer = CATextLayer()
textLayer.foregroundColor = UIColor(hexString: "333333").CGColor
textLayer.string = title
textLayer.alignmentMode = kCAAlignmentLeft
textLayer.contentsScale = UIScreen.mainScreen().scale
textLayer.shouldRasterize = false
textLayer.wrapped = true
textLayer.fontSize = size * AUTOSIZESCALEY
return textLayer
}
is there something I'm doing wrong?
I had a similar problem and used this:
// Set text alignment. Apparently CATextLayer doesn't use value in NSAttributedString.
if let style = text.attribute(NSParagraphStyleAttributeName, atIndex: 0, effectiveRange: nil) as? NSParagraphStyle {
switch style.alignment {
case .Center:
textLayer.alignmentMode = kCAAlignmentCenter
case .Left:
textLayer.alignmentMode = kCAAlignmentLeft
case .Right:
textLayer.alignmentMode = kCAAlignmentRight
default:
break
}
}