UIImage(named:) with period loads wrong image (e.g. for SFSymbols) - ios

I want an easy way to load SFSymbol images with configuration, if possible. Else just load the image normally. So I have this extension and I have all SFSymbols I need in in my assetcatalog with the same name as the symbol (in this case I have "circle" and "circle.fill").
However, in iOS 12 this gives me "circle"!
I assume what's happening is that it counts the dot/period (i.e. circle . fill) as a file extension and assumes the extension is wrong so just grab the circle image with the 'other extension'. Even tho it's not. Is there an easy fix for this? Is this intended?
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let imageView = UIImageView(frame: CGRect(x: 30, y: 30, width: 50, height: 50))
imageView.image = UIImage.image(sfsymbolName: "circle.fill")
view.addSubview(imageView)
}
}
extension UIImage {
static func image(sfsymbolName: String, config: Any? = nil) -> UIImage? {
if #available(iOS 13.0, *), let sfImage = UIImage(systemName: sfsymbolName, withConfiguration: config as? UIImage.Configuration) {
return sfImage
} else {
return UIImage(named: sfsymbolName)
}
}
}

As mentioned in #matt's comment the probably best solution is to just not have images in you asset catalog with a dot (.). Instead use a different character which you can replace at runtime. Just make sure that that character also also doesn't appear in any SFSymbol name (e.g. an underscore _).
Then you can load the image like this:
let image = loadImage(named: "circle_fill")
func loadImage(named imageName: String) -> UIImage? {
let sfSymbolName = imageName.replacingOccurrences(of: "_", with: ".")
if #available(iOS 13.0, *), let img = UIImage(systemName: sfSymbolName)
return img
} else {
return UIImage(named: imageName)
}
}
With the catalog file structure looking like:
|Assets.xcassets
|--circle.imageset
|----...
|--circle_fill.imageset
|----...

Related

How to convert SwiftUI Image back to UIImage? [duplicate]

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)

Delete images programmatically by accessing their tag

Swift 3 / Xcode 8.3.3
When I click on the screen, an image appears (code 1) at the place of the click (if I click 5 times, 5 images appear) and each image has a tag. Now I would like to delete the images one by one at each click on a button (code 2) but only the last image is deleted...
I did a lot of research but none of the results worked for me.
code 1:
var imageView : UIImageView!
var lstTagImage: [Int] = []
var concatenateInt: String = ""
var lstPoint: [Point] = []
concatenateInt = "\(Int(i))\(Int(j))"
lstPoint.append(Point(x: i, y: j, weigth: weigthChip))
imageView = UIImageView(frame:CGRect(x: X,
y: Y,
width: 20, height: 20));
imageView.tag = Int(concatenateInt)!
lstTagImage.append(Int(concatenateInt)!)
imageView.image = UIImage(named: imageString)
self.view.addSubview(imageView)
code 2:
#IBAction func undoButton(_ sender: UIButton) {
if (lstPoint.count != 0){
lstPoint.remove(at: lstPoint.count - 1)
imageView.removeFromSuperview()
} else {
print("list empty")
}
}
An alternate, more elegant approach would be something like this:
let addedImages = [UIImageView]()
// When adding an image to the view
addedImages.append(imageView)
self.view.addSubview(imageView)
Then it's easy to delete.
// When deleting image from view
if let imageView = addedImages.last {
imageView.removeFromSuperview()
addedImages.removeLast()
}
You should use viewWithTag to retreive the last tag, remove that tag from the array and remove the image from its superview.
if lstPoint.count != 0,
let lastTag = lstTagImage.last {
lstPoint.remove(at: lstPoint.count - 1)
lstTagImage.removeLast()
let imageView = view.viewWithTag(lastTag)
imageView.removeFromSuperview()
}

Difficulty loading Image in Swift with Kingfisher

I have a strange problem. I am trying to use Kingfisher in order to load and an cache an Image from Firebase in my app. The problem is that kKingfisher does not download the image. I am using a Placeholder Image that is locally stored and the scroll view displays that Image. If I remove the placeholder part from the command, the app crashes. So I know the Kingfisher function works by at least placing the placeholder Image into the UIImageView but not the image from the URL. Here is the code:
Can you point me to the right direction?
import UIKit
import Kingfisher
class Backprogramme: UIViewController {
override var prefersStatusBarHidden: Bool {
return true
}
#IBOutlet var BackPScroll: UIScrollView!
var imageArray = [UIImage]()
var folie1Image = UIImageView()
override func viewDidLoad() {
super.viewDidLoad()
let folie1URL = URL(string: "https://firebasestorage.googleapis.com/v0/b/backmaster-cdb60.appspot.com/o/Folie1.PNG?alt=media&token=efcb8e93-b817-41f3-a96f-946fd47cf468")!
folie1Image.kf.setImage(with: folie1URL, placeholder: #imageLiteral(resourceName: "first"))
imageArray = [folie1Image.image!]
for i in 0..<imageArray.count {
let imageView = UIImageView()
imageView.image = imageArray[i]
let xPosition = self.view.frame.width * CGFloat(i)
imageView.frame = CGRect(x: xPosition, y: 0, width: self.BackPScroll.frame.width, height: self.BackPScroll.frame.height)
BackPScroll.contentSize.width = BackPScroll.frame.width * CGFloat(i+1)
BackPScroll.addSubview(imageView)
}
// Do any additional setup after loading the view.
}
}
I had the same issue , discovered I had this in the log "The resource could not be loaded because the App Transport Security policy requires the use of a secure connection" .
I fixed it by adding NSAppTransportSecurity as a Dictionary in the info.plist and then I added Allow Arbitrary Loads boolean with YES as a value . It worked instantly.

How to add sticker VIEWS to browser view in swift?

Alright, like everyone Im new to ms stickers in Swift but I am trying to figure out the purpose of / difference between an mssticker and msstickerview. I have read the API here https://developer.apple.com/reference/messages/msstickerview/1648434-sticker but cant find an answer to this relatively simple problem - it seems you can only add MSStickers (not StickerViews) to an MSStickerBrowserView, which is the only way to display them. I however need to add StickerVIEWS because I have a custom sticker view class I am trying to implement.
My stickers are added to my browser view here:
func loadStickers() {
var url: URL?
var i = 1
while true {
url = Bundle.main.url(forResource: "test\(i)", withExtension: "png") //change test for packs
print("URL IS THIS: \(url)")
guard let url = url else { break }
//make it a sticker
let sticker = try! MSSticker(contentsOfFileURL: url, localizedDescription: "")
let stickerView = InstrumentedStickerView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
stickerView.sticker = sticker
stickerView.delegate = self
stickerViews.append(stickerView)
stickers.append(sticker)
i += 1
}
}
func createStickerBrowser() {
let controller = MSStickerBrowserViewController(stickerSize: .regular)
addChildViewController(controller)
view.addSubview(controller.view)
controller.stickerBrowserView.backgroundColor = UIColor.white
controller.stickerBrowserView.dataSource = self
//resize this programmatically later
view.topAnchor.constraint(equalTo: controller.view.topAnchor).isActive = true
view.bottomAnchor.constraint(equalTo: controller.view.bottomAnchor).isActive = true
view.leftAnchor.constraint(equalTo: controller.view.leftAnchor).isActive = true
view.rightAnchor.constraint(equalTo: controller.view.rightAnchor).isActive = true
}
As you can see, I am creating both a sticker and a sticker view for each sticker - my stickers are stored in the stickers array and sticker views in stickerViews array.
Here is how the browser is populated:
func numberOfStickers(in stickerBrowserView: MSStickerBrowserView) -> Int {
return stickers.count
}
func stickerBrowserView(_ stickerBrowserView: MSStickerBrowserView, stickerAt index: Int) -> MSSticker {
return stickers[index] //this isnt displaying stickerveiws only stickers
}
I have tried changing the return type on these methods to StickerView and returning the stickerView array instead
func stickerBrowserView(_ stickerBrowserView: MSStickerBrowserView, stickerAt index: Int) -> MSStickerView {
return stickerViews[index] //this isnt displaying stickerveiws only stickers
}
however this gets me the following error:
messagesviewcontroller does not conform to protocol
msstickerbrowserviewdatasource
Because the required function isn't being implemented as it was before. How does one display sticker views? What am I doing wrong?
You cannot add MSStickerViews to MSStickerBrowserView. In order to use your subclass, you will have to build your own interface per Apple's documentation for MSStickerBrowserView:
If you need additional customizations, you must build your own user interface using MSStickerView objects.
If you want to emulate the look of the browser view, you can just use a UICollectionView and populate the cells with your sticker views

Make emoji symbols grayscale in UILabel

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!
}
}

Resources