How can I append UIView to UITextView? - ios

I have big problem. I want add few UIView to text (UITextView, UILabel)
Each view contains ImageView with corner radius and text.
I want have result like this:
sample from Android
I tried:
Add image with text by NSMutableAttributedString. In this case I can't add corner radius. And all images are from external serwers so it's problem with add to text.
I tried this library: SubviewAttachingTextView. In this case when I added multiple items all items were stacked on top of each other.
Finaly I used WKWebView and I inject HTML with CSS to WebView. But in this solution I have problem with fit content to frame size and is very slow. (for me is the worst solution)
Does anyone have an idea how to develop? Maybe there are some mechanisms in SwiftUI?

You can create a Custom class for a UIView and add subviews that you need inside that, and you can call that class when ever you want, in SwiftUI you can add this very easily by implementing a Label element inside the stack.

I solved my problem. I used NSMutableAttributedString and extensions on UIImage (download images and making circular avatars)
extension UIImage {
convenience init?(withContentsOfUrl url: URL) throws {
let imageData = try Data(contentsOf: url)
self.init(data: imageData)
}
public func withRoundedCorners(radius: CGFloat? = nil) -> UIImage? {
let maxRadius = min(size.width, size.height) / 2
let cornerRadius: CGFloat
if let radius = radius, radius > 0 && radius <= maxRadius {
cornerRadius = radius
} else {
cornerRadius = maxRadius
}
UIGraphicsBeginImageContextWithOptions(size, false, scale)
let rect = CGRect(origin: .zero, size: size)
UIBezierPath(roundedRect: rect, cornerRadius: cornerRadius).addClip()
draw(in: rect)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image
}
}
and my sample code:
class NewNotificationsViewController: UIViewController, UITextViewDelegate {
#IBOutlet weak var testText: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
self.testText.delegate = self
let url = "image-url";
let font = UIFont.systemFont(ofSize: 22)
let attributes: [NSAttributedString.Key: Any] = [
.font: font,
.foregroundColor: UIColor.orange,
.link: "http://test.pl"
]
let myString = NSMutableAttributedString(string: "Text at the beginning ", attributes: attributes)
let imageAttachment = NSTextAttachment()
do {
imageAttachment.image = try UIImage.init(withContentsOfUrl: URL(string: url)!)
} catch {
imageAttachment.image = UIImage(named: "avatar_k")
}
imageAttachment.image = imageAttachment.image?.withRoundedCorners()
imageAttachment.bounds = CGRect(x: 0, y: -8, width: 32, height: 32)
let imageString = NSAttributedString(attachment: imageAttachment)
myString.append(imageString)
myString.append(NSAttributedString(string: " THE END!!!", attributes: attributes))
self.testText.attributedText = myString
}
func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange) -> Bool {
print("DEBUG", URL.absoluteString)
return false
}
}

Related

How can I insert a UIView into an MSMessage?

I am trying to build an iMessage app that allows a person to press a button which inserts a blue UIView into the iMessage text field. I am not sure how I can get the UIView in the iMessage text field.
I've tried to convert the UIView to a UIImage, then convert that to an MSSticker, however, I need a URL for the sticker and since I created the UIImage in code, I do not have a URL for the path.
Here, I create the UIView:
func createOval() -> UIImage {
let blueOval = UIView(frame: CGRect(x: 5, y: 0, width: 10, height: 10))
blueOval.backgroundColor = .black
let label = UILabel()
label.text = "Hello"
blueOval.addSubview(label)
let image = blueOval.asImage()
return tagImage
}
The extension that enables the asImage() conversion:
extension UIView {
func asImage() -> UIImage {
if #available(iOS 10.0, *) {
let renderer = UIGraphicsImageRenderer(bounds: bounds)
return renderer.image { rendererContext in
layer.render(in: rendererContext.cgContext)
}
} else {
UIGraphicsBeginImageContext(self.frame.size)
self.layer.render(in:UIGraphicsGetCurrentContext()!)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return UIImage(cgImage: image!.cgImage!)
}
}
}
How can I insert that UIView into the iMessage text field?

How to generate an UIImage from custom text in Swift

I am trying to generate an UIImage from custom text in Swift3.
Using iOS Controls, It's possible to create an UIImage, below is the code:
class func imageWithText(txtField: UITextField) -> UIImage {
UIGraphicsBeginImageContextWithOptions(txtField.bounds.size, false, 0.0)
txtField.layer.render(in: UIGraphicsGetCurrentContext()!)
let img = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return img!
}
Note: I know it's also possible to add some text to an image, but I don't want to do like this.
Can someone help me to resolve this? Thank you!
Here is a String extension that can be used as shown below to create UIImage instances from a String instance, without the need for UI controls like UITextField or UILabel:
var image: UIImage? =
"Test".image(withAttributes: [
.foregroundColor: UIColor.red,
.font: UIFont.systemFont(ofSize: 30.0),
], size: CGSize(width: 300.0, height: 80.0))
// Or
image = "Test".image(withAttributes: [.font: UIFont.systemFont(ofSize: 80.0)])
// Or
image = "Test".image(size: CGSize(width: 300.0, height: 80.0))
// Or even just
image = "Test".image()
Below are two possible implementations for achieving the desired effect demonstrated above:
UIGraphicsImageRenderer Method (more performant and recommended implementation by Apple)
extension String {
/// Generates a `UIImage` instance from this string using a specified
/// attributes and size.
///
/// - Parameters:
/// - attributes: to draw this string with. Default is `nil`.
/// - size: of the image to return.
/// - Returns: a `UIImage` instance from this string using a specified
/// attributes and size, or `nil` if the operation fails.
func image(withAttributes attributes: [NSAttributedString.Key: Any]? = nil, size: CGSize? = nil) -> UIImage? {
let size = size ?? (self as NSString).size(withAttributes: attributes)
return UIGraphicsImageRenderer(size: size).image { _ in
(self as NSString).draw(in: CGRect(origin: .zero, size: size),
withAttributes: attributes)
}
}
}
UIGraphicsImageContext Method (old-school; included for thoroughness)
extension String {
/// Generates a `UIImage` instance from this string using a specified
/// attributes and size.
///
/// - Parameters:
/// - attributes: to draw this string with. Default is `nil`.
/// - size: of the image to return.
/// - Returns: a `UIImage` instance from this string using a specified
/// attributes and size, or `nil` if the operation fails.
func image(withAttributes attributes: [NSAttributedString.Key: Any]? = nil, size: CGSize? = nil) -> UIImage? {
let size = size ?? (self as NSString).size(withAttributes: attributes)
UIGraphicsBeginImageContext(size)
(self as NSString).draw(in: CGRect(origin: .zero, size: size),
withAttributes: attributes)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image
}
}
You can use this function, you can send any text to this function, inside it i create UILabel and set text attribute as you like
func imageWith(name: String?) -> UIImage? {
let frame = CGRect(x: 0, y: 0, width: 100, height: 100)
let nameLabel = UILabel(frame: frame)
nameLabel.textAlignment = .center
nameLabel.backgroundColor = .lightGray
nameLabel.textColor = .white
nameLabel.font = UIFont.boldSystemFont(ofSize: 40)
nameLabel.text = name
UIGraphicsBeginImageContext(frame.size)
if let currentContext = UIGraphicsGetCurrentContext() {
nameLabel.layer.render(in: currentContext)
let nameImage = UIGraphicsGetImageFromCurrentImageContext()
return nameImage
}
return nil
}

How do I create a custom map view pin with a dynamic element?

I am trying to create a custom pin which has a label in the center of the pin which will change with every pin. The label will just be the pin number, so if I have 8 pins each pin will have a number in the center i.e. 1st pin will have 1, 2nd pin will have 2, etc. The issue is that the regular pin appears, not the custom pin.
override func viewDidLoad() {
super.viewDidLoad()
mapView.delegate = self
loadPins()
}
func loadPins() {
let annotation = CustomPin()
annotation.coordinate = CLLocationCoordinate2D(latitude: 30.2531419, longitude: -97.78785789999999)
mapView.addAnnotation(annotation)
}
class AnView: UIView {
#IBOutlet weak var countLabel: UILabel!
#IBOutlet weak var pinImage: UIImageView!
override func draw(_ rect: CGRect) {
//Draw function
}
}
class CustomPin: MKPointAnnotation {
var pin = AnView(frame: CGRect(x: 0, y: 0, width: 200, height: 200))
}
The annotation (your CustomPin) gets the latitude/longitude values.
The annotation view (which your code doesn't show) gets the label or image you want to show on the map.
Since the Maps and Location Programming Guide from the comments gives all of its examples in Objective-C, you might want to also look at this RayWenderlich.com tutorial for Swift help:
MapKit Tutorial: Getting Started
You can draw the number over the map pin image. Just create a function (or extension) to add text over the image like this:
func addTextOverImage(_ text: String, overImage image: UIImage) -> UIImage {
//set scale and context
let scale = UIScreen.main.scale
UIGraphicsBeginImageContextWithOptions(image.size, false, scale)
image.draw(in: CGRect(origin: CGPoint.zero, size: image.size))
//set font face, size and color
let textColor = UIColor.black
let textFont = UIFont.boldSystemFont(ofSize: 12)
//define the text attributes
let paragraph = NSMutableParagraphStyle()
paragraph.alignment = .center
let textFontAttributes = [
NSFontAttributeName: textFont,
NSForegroundColorAttributeName: textColor,
NSParagraphStyleAttributeName: paragraph
] as [String : Any]
//set the text position (in the image rect)
let textPoint=CGPoint.init(x: 0.0, y: image.size.height/2)
//create the rect to draw the text
let rect = CGRect(origin: textPoint, size: image.size)
//draw the text over the image, in the rect just defined
text.draw(in: rect, withAttributes: textFontAttributes)
let newImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
//return image with text
return newImage!
}
You may need to adjust the textPoint in the function to get the text positioned where you want it to be.

UITableView cell is highlighted on selection, but the UIImageView remains opaque. How to highlight the entire cell?

The image is set on the imageView via a URL. When I do not set an image, the tablecell highlights just fine, but when the image is applied to it, it seems to become opaque, almost like the image has a higher z-index compared to the highlight view that the tablview is applying. Does this mean I will have to use a custom highlight?
When the cell is highlighted it sets the highlighted property of labels and images.
You need to provide a highlightedImage to the UIImageView or look at more complicated solutions.
Maybe a bit late for the party but this extension should work for your case.
It loads async an image from an URL and then create the corresponding highlighted image by using the default gray color. If you want you can change the overlay color by working on the fillcolor parameter.
extension UIImageView {
func loadImage(url: String, placeHolder: UIImage?) {
let anURL = URL(string: url)!
self.image = placeHolder
let aRequest = URLRequest(url: anURL, cachePolicy: .returnCacheDataElseLoad, timeoutInterval: 10)
URLSession(configuration: .default).dataTask(with: aRequest, completionHandler: { (data, response, error) in
DispatchQueue.main.async {
if let anError = error as NSError? {
AlertController.presentOkayAlert(error: anError)
}
else if let tmpData = data, let anImage = UIImage(data: tmpData) {
self.image = anImage
self.highlightedImage = self.filledImage(source: anImage, fillColor: UIColor.colorWithHexString("#c0c0c0", alpha: 0.6))
}
}
}).resume()
}
func filledImage(source: UIImage, fillColor: UIColor) -> UIImage {
UIGraphicsBeginImageContextWithOptions(source.size, false, UIScreen.main.scale)
let context = UIGraphicsGetCurrentContext()
fillColor.setFill()
context!.translateBy(x: 0, y: source.size.height)
context!.scaleBy(x: 1.0, y: -1.0)
context!.setBlendMode(CGBlendMode.colorBurn)
let rect = CGRect(x: 0, y: 0, width: source.size.width, height: source.size.height)
context!.draw(source.cgImage!, in: rect)
context!.setBlendMode(CGBlendMode.normal)
context!.addRect(rect)
context!.drawPath(using: CGPathDrawingMode.fill)
let coloredImg : UIImage = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
return coloredImg
}
}

Swift - Playground - Core Graphics / Core Text / Custom View

Question
I'm attempting to make a custom UIView component in swift playground. I've been able to figure out how to do everything except for render text correctly. I'm not sure if i'm supposed to do this via Core Graphics or Core Text or exactly how to get it working correctly.
As you can see in the image below I want to render text both upside down and right side up on either side of my custom View Component
I've tried various ways to get the text to work but I keep running aground. If somebody would be able to show me how to modify my playground to add a few text rendering that would be awesome.
Playground Code
// Playground - noun: a place where people can play
import Foundation
import UIKit
class RunwayView : UIView {
var northText : String?
var southText : String?
///Draws the "runway"
override func drawRect(rect: CGRect) {
// Setup graphics context
var ctx = UIGraphicsGetCurrentContext()
// clear context
CGContextClearRect(ctx, rect)
let parentVieBounds = self.bounds
let width = CGRectGetWidth(parentVieBounds)
let height = CGRectGetHeight(parentVieBounds)
// Setup the heights
let endZoneHeight : CGFloat = 40
/* Remember y is negative and 0,0 is upper left*/
//Create EndZones
CGContextSetRGBFillColor(ctx, 0.8, 0.8, 0.8, 1)
CGContextFillRect(ctx, CGRectMake(0, 0, width, endZoneHeight))
CGContextFillRect(ctx, CGRectMake(0, height-endZoneHeight, width, endZoneHeight))
var northString = NSMutableAttributedString(string: "36")
var attrs = [NSFontAttributeName : UIFont.systemFontOfSize(16.0)]
var gString = NSMutableAttributedString(string:"g", attributes:attrs);
var line = CTLineCreateWithAttributedString(gString)
CGContextSetTextPosition(ctx, 10, 50);
CTLineDraw(line, ctx);
// Clean up
}
}
var outerFrame : UIView = UIView(frame: CGRectMake(20,20,400,400))
var runway1 : RunwayView = RunwayView(frame: CGRectMake(0,0,30,260))
var runway2 : RunwayView = RunwayView(frame: CGRectMake(80,0,30,340))
runway1.transform = CGAffineTransformConcat(CGAffineTransformMakeTranslation(20, 200),
CGAffineTransformMakeRotation(-0.785398163))
outerFrame.addSubview(runway1)
runway2.transform = CGAffineTransformConcat(CGAffineTransformMakeTranslation(120, 140),
CGAffineTransformMakeRotation(-0.585398163))
outerFrame.addSubview(runway2)
outerFrame.backgroundColor = UIColor.yellowColor()
outerFrame.clipsToBounds = true
// View these elements
runway1
outerFrame
runway1
I wrote a utility function for my app to draw text using CoreText.
It returns the text size for further processing:
func drawText(context: CGContextRef, text: NSString, attributes: [String: AnyObject], x: CGFloat, y: CGFloat) -> CGSize {
let font = attributes[NSFontAttributeName] as UIFont
let attributedString = NSAttributedString(string: text, attributes: attributes)
let textSize = text.sizeWithAttributes(attributes)
// y: Add font.descender (its a negative value) to align the text at the baseline
let textPath = CGPathCreateWithRect(CGRect(x: x, y: y + font.descender, width: ceil(textSize.width), height: ceil(textSize.height)), nil)
let frameSetter = CTFramesetterCreateWithAttributedString(attributedString)
let frame = CTFramesetterCreateFrame(frameSetter, CFRange(location: 0, length: attributedString.length), textPath, nil)
CTFrameDraw(frame, context)
return textSize
}
Call it like so:
var textAttributes: [String: AnyObject] = [
NSForegroundColorAttributeName : UIColor(white: 1.0, alpha: 1.0).CGColor,
NSFontAttributeName : UIFont.systemFontOfSize(17)
]
drawText(ctx, text: "Hello, World!", attributes: textAttributes, x: 50, y: 50)
Hope that helps you.

Resources