I would like to use Apple's built-in emoji characters (specifically, several of the smileys, e.g. \ue415) in a UILabel but I would like the emojis to be rendered in grayscale.
I want them to remain characters in the UILabel (either plain text or attributed is fine). I'm not looking for a hybrid image / string solution (which I already have).
Does anyone know how to accomplish this?
I know you said you aren't looking for a "hybrid image solution", but I have been chasing this dragon for a while and the best result I could come up with IS a hybrid. Just in case my solution is somehow more helpful on your journey, I am including it here. Good luck!
import UIKit
import QuartzCore
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// the target label to apply the effect to
let label = UILabel(frame: view.frame)
// create label text with empji
label.text = "🍑 HELLO"
label.textAlignment = .center
// set to red to further show the greyscale change
label.textColor = .red
// calls our extension to get an image of the label
let image = UIImage.imageWithLabel(label: label)
// create a tonal filter
let tonalFilter = CIFilter(name: "CIPhotoEffectTonal")
// get a CIImage for the filter from the label image
let imageToBlur = CIImage(cgImage: image.cgImage!)
// set that image as the input for the filter
tonalFilter?.setValue(imageToBlur, forKey: kCIInputImageKey)
// get the resultant image from the filter
let outputImage: CIImage? = tonalFilter?.outputImage
// create an image view to show the result
let tonalImageView = UIImageView(frame: view.frame)
// set the image from the filter into the new view
tonalImageView.image = UIImage(ciImage: outputImage ?? CIImage())
// add the view to our hierarchy
view.addSubview(tonalImageView)
}
}
extension UIImage {
class func imageWithLabel(label: UILabel) -> UIImage {
UIGraphicsBeginImageContextWithOptions(label.bounds.size, false, 0.0)
label.layer.render(in: UIGraphicsGetCurrentContext()!)
let img = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return img!
}
}
Related
since the documentation on swiftUI isn't great yet I wanted to ask how I can convert an "image" to an "UIImage" or how to convert an "image" to pngData/jpgData
let image = Image(systemName: "circle.fill")
let UIImage = image as UIImage
There is no direct way of converting an Image to UIImage. instead, you should treat the Image as a View, and try to convert that View to a UIImage.
Image conforms to View, so we already have the View we need. now we just need to convert that View to a UIImage.
We need 2 components to achieve this. First, a function to change our Image/View to a UIView, and second one, to change the UIView we created to UIImage.
For more Convenience, both functions are declared as Extensions to their appropriate types.
extension View {
// This function changes our View to UIView, then calls another function
// to convert the newly-made UIView to a UIImage.
public func asUIImage() -> UIImage {
let controller = UIHostingController(rootView: self)
controller.view.frame = CGRect(x: 0, y: CGFloat(Int.max), width: 1, height: 1)
UIApplication.shared.windows.first!.rootViewController?.view.addSubview(controller.view)
let size = controller.sizeThatFits(in: UIScreen.main.bounds.size)
controller.view.bounds = CGRect(origin: .zero, size: size)
controller.view.sizeToFit()
// here is the call to the function that converts UIView to UIImage: `.asUIImage()`
let image = controller.view.asUIImage()
controller.view.removeFromSuperview()
return image
}
}
extension UIView {
// This is the function to convert UIView to UIImage
public func asUIImage() -> UIImage {
let renderer = UIGraphicsImageRenderer(bounds: bounds)
return renderer.image { rendererContext in
layer.render(in: rendererContext.cgContext)
}
}
}
How To Use?
let image: Image = Image("MyImageName") // Create an Image anyhow you want
let uiImage: UIImage = image.asUIImage() // Works Perfectly
Bonus
As i said, we are treating the Image, as a View. In the process, we don't use any specific features of Image, the only thing that is important is that our Image is a View (conforms to View protocol).
This means that with this method, you can not only convert an Image to a UIImage, but also you can convert any View to a UIImage.
var myView: some View {
// create the view here
}
let uiImage = myView.asUIImage() // Works Perfectly
Such thing is not possible with SwiftUI, and I bet it will never be. It goes againts the whole framework concept. However, you can do:
let uiImage = UIImage(systemName: "circle.fill")
let image = Image(uiImage: uiImage)
I have to render create a huge amount(~10000) of UIImage objects, which are later added to a map using MapBox. The image are created by rendering a UILabel to an UIImage (sadly, the label can not directly be rendered on the map in the right font).
I imagined that, because I am not adding the views to the hierarchy until they are rendered on the map, I could create the views on a background thread in order to not have the UI freeze. However, this seems not to be possible.
My question is, is there a way to create a huge amount of UIImage objects by rendering a UILabel to an image without freezing the UI? Thanks for any help!
My code to render a UIView to an image is as follows:
private func render(_ view: UIView) -> UIImage? {
UIGraphicsBeginImageContext(view.frame.size)
guard let currentContext = UIGraphicsGetCurrentContext() else {return nil}
view.layer.render(in: currentContext)
return UIGraphicsGetImageFromCurrentImageContext()
}
And an example of a label I render to an image:
bigItemList.forEach { item in
// work to process the item..
// I have to call the rendering om the main thread, which causes the UI to freeze
DispatchQueue.main.async {
addImage(createLabel(text: label))
}
}
func createLabel(text: String?) -> UIImage? {
let label = UILabel()
label.textAlignment = .center
label.font = .boldSystemFont(ofSize: 14)
label.textColor = .mainBlue
label.text = text
label.sizeToFit()
label render(depthLabel)
}
UILabel is not safe to access on any non-main thread. The tool you want here is CATextLayer which will do all the same things (with a slightly clunkier syntax) while being thread-safe. For example:
func createLabel(text: String) -> UIImage? {
let label = CATextLayer()
let uiFont = UIFont.boldSystemFont(ofSize: 14)
label.font = CGFont(uiFont.fontName as CFString)
label.fontSize = 14
label.foregroundColor = UIColor.blue.cgColor
label.string = text
label.bounds = CGRect(origin: .zero, size: label.preferredFrameSize())
let renderer = UIGraphicsImageRenderer(size: label.bounds.size)
return renderer.image { context in
label.render(in: context.cgContext)
}
}
This is safe to run on any queue.
You most probably are already in main queue. Try execuring the loop in another one:
DispatchQueue.init(label: "Other queue").async {
bigItemList.forEach { item in
// work to process the item..
// I have to call the rendering om the main thread, which causes the UI to freeze
DispatchQueue.main.async {
addImage(createLabel(text: label))
}
}
}
EDIT: If it is already in another queue, you are also missing the UIGraphicsEndImageContext() call into render:
private func render(_ view: UIView) -> UIImage? {
UIGraphicsBeginImageContext(view.frame.size)
guard let currentContext = UIGraphicsGetCurrentContext() else {return nil}
view.layer.render(in: currentContext)
let img = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return img
}
EDIT: Another thing I see is that label allocation is the most time consuming in your loop. Try allocating it once since it's reusable:
let label = UILabel()
func createLabel(text: String?) -> UIImage? {
label.textAlignment = .center
...
...
}
I am trying to achieve an editable textView with the ability to embed a 'Custom View' as shown:
This view encompasses 2 textfields (it is showing a word definition). The view dynamically changes dependant on what definition is selected. I have currently been able to add it to the textView as an NSAttachment as an image (since I can't attach a custom view to a textView) but if I go about it this way I have no way of accessing what the text is inside the custom view if I need to update the view.
Can anyone give me a solution where I would be able to store the info that is in the custom view somehow? I want to make sure in future I would be able to actually access the data in case the view design changes and I wanted to update it. Do I need to change to a WebView and somehow create a custom parse in HTML that looks for definition info and then formats it?
EDIT: Code used to create NSAttachment. The returnView() function returns a view object as shown in red in the photo above. The rest of the viewController is just a textView which saves the NSAttributedString to a Core Data Object.
func viewToNSAttachment() {
let view = returnView()
view.layoutIfNeeded()
// Render Image
let renderer = UIGraphicsImageRenderer(size: view.frame.size)
let image = renderer.image {
view.layer.render(in: $0.cgContext)
}
// Create Attachment
let attachment = NSTextAttachment()
attachment.image = image
attachment.bounds = CGRect(origin: .zero, size: image.size)
// Current Attributed String
let atStr = NSMutableAttributedString(attributedString: textView.attributedText)
// Insert Attachment
let attachmentAtStr = NSAttributedString(attachment: attachment)
if let selectedRange = textView.selectedTextRange {
let cursorIndex = textView.offset(from: textView.beginningOfDocument, to: selectedRange.start)
atStr.insert(attachmentAtStr, at: cursorIndex)
atStr.addAttributes(self.textView.typingAttributes, range: NSRange(location: cursorIndex, length: 1))
} else {
atStr.append(attachmentAtStr)
}
textView.attributedText = atStr
}
I want to implement simple sharing function. In my app if user long pressed on UITableViewCell, it's present UIActionController with some buttons.
The first one allows to share content with friends (UIActivityViewController)
My goal is to save cell text with brand watermark in the right corner.
For now, I'm using this extension for converting UIView to UImage:
extension UIView {
func convertToImage() -> 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!)
}
}
}
But I have some problems with it. It's works only when the view presented on screen. If I try to get an image from UIView which is not shown on the screen, I get an empty variable.
Even with vc:
let vc = ViewController()
let image = vc.view.convertToImage()
//Image empty
I don't want that user see content with watermark on the screen, I need watermark be added only to the camera roll.
Can I do it?
Can a VisualEffect (Blur) be added to a UIImageView using a IBDesignable class? (I've search all over and found nothing so far) I've tried adding it a layer.addSublayer(layer: CALayer) but there is not CALayer for visual effect
You can try making a subclass of UIImageView like the following:
#IBDesignable
class Blur: UIImageView {
#IBInspectable var blurImage:UIImage?{
didSet{
updateImage(blurImage!)
}
}
func updateImage(imageBlur:UIImage){
let imageToBlur:CIImage = CIImage(image: imageBlur)!
let blurFilter:CIFilter = CIFilter(name: "CIGaussianBlur")!
blurFilter.setValue(imageToBlur, forKey: "inputImage")
let resultImage:CIImage = blurFilter.valueForKey("outputImage")! as! CIImage
self.image = UIImage(CIImage: resultImage)
}
}
You will have to play around with it to get it the way you want.
More information about CoreImageFilter can be found here