Swift, Extracting Glyphs from a Font by Name - swift

I am attempting to glyphs from the SF Symbol front using the code sample below provided by an extract https://github.com/davedelong/sfsymbols. I have isolated the code that I can't get to work with values extracted from a run time.
I have condensed the code down to only the statements required to produce the problem.
This issue seems to be the name I am providing to the final statement in CTFontGetGlyphWithName, Every combination of values I have attempted returns 0.
Please note that you have to have the SF Symbols application installed for it to acquire the initial bundle.
Please excuse the code quality, this is just to create the shortest possible example of code to reproduce the problem.
thanks.
I have followed quite a few snipped and tutorials but there doesn't seem to be a great deal of practical examples of these more advanced functions available.
public class StackOverflow {
public func Test() {
let maybeCFURLs = LSCopyApplicationURLsForBundleIdentifier("com.apple.SFSymbols" as CFString, nil)?.takeRetainedValue()
let cfURLs = maybeCFURLs as? Array<URL>
let fontFile = cfURLs!.compactMap { url -> URL? in
guard let appBundle = Bundle(url: url) else { return nil }
return appBundle.url(forResource: "SFSymbolsFallback", withExtension: "ttf")
}
let attributes = [
kCTFontTraitsAttribute: [
kCTFontWeightTrait: NSFont.Weight.regular.rawValue
]
]
let provider = CGDataProvider(url: fontFile.first! as CFURL)
let cgFont = CGFont(provider!)
let ctAttributes = CTFontDescriptorCreateWithAttributes(attributes as CFDictionary)
let font = CTFontCreateWithGraphicsFont(cgFont!, 32, nil, ctAttributes)
// let name = "uni.plus.square.fill.medium" as CFString - Does not work
//var name = "square.fill.medium" as CFString - Does not work
let glyphName = "plus.square.fill.medium" as CFString
var glyph = CTFontGetGlyphWithName(font, glyphName )
}
}

Related

MacOS how is KIND implemented

I am trying to write a piece of code that instead of checking file extensions for the many different types of image files, but instead looking at the file attributes. What I can’t figure out from searching the docs is if KIND:Image is really a file attribute or simply a construct Apple created in the FinderApp to make things easier for the user.
I wrote a snippet that pulls the attributes for files with an extension of jpeg and for each file the fileType is returned as NSFileTypeRegular.
let attr = try filemanager.attributesOfItem(atPath: pathConfig+"/"+file) as NSDictionary
if file.hasSuffix(ext) {
   print ("Adding \(file) [ \(attr.fileSize()) \(attr.fileType())]")
   print ( attr.fileModificationDate() )
}
Does anybody know if MacOS retains an attribute for the category a file falls in to. e.g. IMAGE, DOCUMENT etc.
To achieve a functionality similar to the Kind search tag in Finder you can use UTType (Link to reference).
You can get the UTType of a file by initialising it with the file extension:
let fileType = UTType(filenameExtension: fileURL.pathExtension)
The cool thing about UTTypes is that they have a hierarchy, for example, UTType.jpeg is a subtype of UTType.image, along with others like .png.
If you want to check if a file is any kind of image, you can do it like this
let isImage = fileType.conforms(to: .image)
You can check the list for the kind of types you want to support as "Kinds", and filter using those UTTypes
This was my final solution based on the information provided by #EmilioPelaez I am not completely comfortable with Swift especially the unwrapping operations so if the code looks weird that might be why.
func imagesInDir(path: String?) -> [String] {
if let path {
let filemanager: FileManager = FileManager()
let files = filemanager.enumerator(atPath: path)
var array = [String]()
var urlFile: NSURL
while let file = files?.nextObject() as? String {
urlFile = NSURL(fileURLWithPath: file, isDirectory: false)
if (urlFile.isFileURL) {
if let pathExt = urlFile.pathExtension {
if let fileType = UTType(filenameExtension: pathExt) {
let isImage = fileType.conforms(to: .image)
if (isImage){
array.append(file)
print ("\(array.count) \(fileType)")
}
}
}
}
}
}
return array
}

iOS Swift - reading a persisted data model after having altered properties in that model

Thanks in advance for your help.
I want to persist data such as a user's stats. Let's say I have a data model, a class 'Stats' with a few properties, and it gets saved to the user's device. Supposing that I've released the app, users are recording their stats but then later on I want to make changes to the class - more or fewer properties, maybe even renaming them (etc.), ahead of a new build release. But after these changes have been made, the type 'Stats' is now different to the one the users have saved on their device, so it won't be able to decode and it seems like all the user's previous data up until that point would be lost/unattainable.
How can I add make these kinds of changes to the class in a way in which the PropertyListDecoder will still be able to decode the stats that are still on the user's device?
This is basically what I have:
class Stat: Codable {
let questionCategory = questionCategory()
var timesAnsweredCorrectly: Int = 0
var timesAnsweredFirstTime: Int = 0
var timesFailed: Int = 0
static func saveToFile(stats: [Stat]) {
let propertyListEncoder = PropertyListEncoder()
let encodedSettings = try? propertyListEncoder.encode(stats)
try? encodedSettings?.write(to: archiveURL, options: .noFileProtection)
}
static func loadFromFile() -> [Stat]? {
let propertyListDecoder = PropertyListDecoder()
if let retrievedSettingsData = try? Data(contentsOf: archiveURL), let decodedSettings = try? propertyListDecoder.decode([Stat].self, from: retrievedSettingsData) {
return decodedSettings
} else {
return nil
}
}
}
static let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
static let archiveURL = documentsDirectory.appendingPathComponent("savedVerbStats").appendingPathExtension("plist")
It seems that even just adding a new property to 'Stat' will cause the user's previous persisted data to become un-decodable as type 'Stat', and loadFromFile() will return nil.
Any advice would be great! I'm sure I'm going about this the wrong way. I figured that the array [Stat] would be too big to persist in UserDefaults but even then I think this problem would still exist... Can't find anything about it online; it seems that once you've got your users using a persisted class you can't then alter it. I tried using default values for the new properties but the result is the same.
The only solution I can think of is breaking down the class into literals and saving all these in some kind of a tuple/dictionary form instead. Then I would decode that raw data, and have a function to assemble and create the class out of whatever relevant data can still be taken from the old version of the 'Stat' type. Seems like a big workaround and I'm sure you guys know a much a better way.
Thanks!!
Removing a property is easy enough. Just delete its definition from the Stat class and existing data for that property will be deleted when you read and save stats again.
The key to adding new properties is to make them optional. For example:
var newProperty: Int?
When a previously existing stat is decoded the first time, this property will be nil, but all the other properties will be set correctly. You can set and save the new property as needed.
It may be a minor inconvenience to have all new properties as optional, but it opens the door to other possible migration schemes without losing data.
EDIT: Here is a more complicated migration scheme that avoids optionals for new properties.
class Stat: Codable {
var timesAnsweredCorrectly: Int = 0
var timesAnsweredFirstTime: Int = 0
var timesFailed: Int = 0
//save all stats in the new Stat2 format
static func saveToFile(stats: [Stat2]) {
let propertyListEncoder = PropertyListEncoder()
let encodedSettings = try? propertyListEncoder.encode(stats)
try? encodedSettings?.write(to: archiveURL, options: .noFileProtection)
}
//return all stats in the new Stat2 format
static func loadFromFile() -> [Stat2]? {
let propertyListDecoder = PropertyListDecoder()
//first, try to decode existing stats as Stat2
if let retrievedSettingsData = try? Data(contentsOf: archiveURL), let decodedSettings = try? propertyListDecoder.decode([Stat2].self, from: retrievedSettingsData) {
return decodedSettings
} else if let retrievedSettingsData = try? Data(contentsOf: archiveURL), let decodedSettings = try? propertyListDecoder.decode([Stat].self, from: retrievedSettingsData) {
//since we couldn't decode as Stat2, we decoded as Stat
//convert existing Stat instances to Stat2, giving the newProperty an initial value
var newStats = [Stat2]()
for stat in decodedSettings {
let newStat = Stat2()
newStat.timesAnsweredCorrectly = stat.timesAnsweredCorrectly
newStat.timesAnsweredFirstTime = stat.timesAnsweredFirstTime
newStat.timesFailed = stat.timesFailed
newStat.newProperty = 0
newStats.append(newStat)
}
return newStats
} else {
return nil
}
}
static let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
static let archiveURL = documentsDirectory.appendingPathComponent("savedVerbStats").appendingPathExtension("plist")
}
class Stat2: Stat {
var newProperty: Int = 0
}
Following on from Mike's response, I came up with a migration scheme that seems to solve the issue of the optionals and doesn't require any new classes every time the data model is altered. Part of the issue is that a developer may change or add properties to a class that gets persisted and Xcode would never flag this as an issue, which may result in your user's app trying to read the previous data class saved to the device, returning nil and in all likelihood overwriting all the data in question with a reformatted model.
Instead of writing the class (e.g. Stat) to the disk (which is what Apple suggests in its teaching resources), I save a new struct "StatData" which comprises only optional properties of the data that I want to write to the file:
struct StatData: Codable {
let key: String
let timesAnsweredCorrectly: Int?
let timesAnsweredFirstTime: Int?
let timesFailed: Int?
}
That way I can read the properties from the file and any added or deleted properties from the struct would just return nil instead of making the entire struct unreadable. Then I have two functions to convert 'StatData' into 'Stat' (and back), providing default values in case any have been returned nil.
static func convertToData(_ stats: [Stat]) -> [StatData] {
var data = [StatData]()
for stat in stats {
let dataItem = StatData(key: stat.key, timesAnsweredCorrectly: stat.timesAnsweredCorrectly, timesAnsweredFirstTime: stat.timesAnsweredFirstTime, timesFailed: stat.timesFailed)
data.append(dataItem)
}
return data
}
static func convertFromData(_ statsData: [StatData]) -> [Stat] {
// if any of these properties weren't previously saved to the device, they will return the default values but the rest of the data will remain accessible.
var stats = [Stat]()
for item in statsData {
let stat = stat.init(key: item.key, timesAnsweredCorrectly: item.timesAnsweredCorrectly ?? 0, timesAnsweredFirstTime: item.timesAnsweredFirstTime ?? 0, timesFailed: item.timesFailed ?? 0)
stats.append(stat)
}
return stats
}
I then call these functions when reading or saving the data to disk. The advantage of this is that I can choose which properties from the Stat class that I want to save, and because the StatData model is a struct, the memberwise initializer will warn any developer who changes the data model that they will also need to account for the change when reading old data from the file.
This seems to do the job. Any comments or other suggestions would be appreciated

How to properly use CFStringGetCString in swift?

I am looking at the docs for
CFStringGetCString
and AXUIElementCopyAttributeValue.
CFStringGetCString takes the param buffer: UnsafeMutablePointer<Int8>!
AXUIElementCopyAttributeValue takes the param value: UnsafeMutablePointer<CFTypeRef?>
For the latter, I can do a call like this:
var value: CFTypeRef?
let err = AXUIElementCopyAttributeValue(element, attribute as CFString, &value);
This satisfies the doc asking for an UnsafeMutablePointer of type CFTypeRef.
However I can't get the same logic to apply by doing
let buffer: Int8!
CFStringGetCString(attribute as! CFString, &buffer, 2048, CFStringBuiltInEncodings.UTF8.rawValue)
I also tried
let buffer: Int8?
CFStringGetCString(attribute as! CFString, &buffer!, 2048, CFStringBuiltInEncodings.UTF8.rawValue)
Either way, it complains about using buffer before it's initialized, even though it never complained about value in the working method with similar param requirements.
All the working examples I've seen for CFStringGetCString are using objective-c like syntax with *. Not sure what the proper swift way is here.
I also tried this way to get the value I wanted:
let app = AXUIElementCreateSystemWide();
var valueString = "";
var value: CFTypeRef?
// An exception on execution happens here when passing app
// Passing in the element I clicked instead of app
// yields error -25205 (attributeunsupported)
let err = AXUIElementCopyAttributeValue(app, "AXFocusedApplication" as CFString, &value);
if (err == AXError.success) {
valueString = value! as! NSString as String;
} else {
print("ERROR!");
print(err.rawValue);
}
return valueString;
Why are you torturing yourself with CFStringGetCString? If you have a CFString in Swift, you can cast it to a String and get a C string from that:
let cString: [Int8] = (cfString as String).cString(using: .utf8)!
Note also that the value of the kAXFocusedApplicationAttribute is not a CFString. It is an AXUIElement.
Here's my playground test:
import Foundation
import CoreFoundation
let axSystem = AXUIElementCreateSystemWide()
var cfValue: CFTypeRef?
AXUIElementCopyAttributeValue(axSystem, kAXFocusedApplicationAttribute as CFString, &cfValue)
if let cfValue = cfValue, CFGetTypeID(cfValue) == AXUIElementGetTypeID() {
let axFocusedApplication = cfValue
print(axFocusedApplication)
}
The first time I executed this playground, I got a system dialog box telling me that I need to give Xcode permission to control my computer. I went to System Preferences > Security & Privacy > Privacy > Accessibility, found Xcode at the bottom of the list, and turned on its checkbox.
Here's the output of the playground:
<AXUIElement Application 0x7fb2d60001c0> {pid=30253}
I assume you're on macOS since the AXUI API is only available on macOS. If you just want the name of the front application as a string, you can do this:
if let frontAppName = NSWorkspace.shared.frontmostApplication?.localizedName {
print(frontAppName)
}

How to generate dataMatrix using CIFilter?

I am getting error:
Value for key inputBarcodeDescriptor of type CIDataMatrixCodeDescriptor is not yet supported
let string = "tempValue&123"
let data = string.data(using: String.Encoding.ascii, allowLossyConversion: false)
guard let data = data else {
return nil
}
let descriptor = CIDataMatrixCodeDescriptor(payload: data, rowCount: 1, columnCount: 1, eccVersion: CIDataMatrixCodeDescriptor.ECCVersion(rawValue: 0))
let inputParameter = ["inputBarcodeDescriptor": descriptor]
let datafilter = CIFilter(name: "CIBarcodeGenerator", parameters: inputParameter)
let image = datafilter?.outputImage
print(image)
Well, I think you should just believe this runtime warning. You can't create a barcode using a CIDataMatrixCodeDescriptor; the class is documented but it isn't actually working. Use a different CIBarcodeDescriptor subclass instead (such as CIAztecCodeDescriptor).
wow, I've just ran into this issue... in 2022 :D
message is still
[api] Value for key inputBarcodeDescriptor of type CIDataMatrixCodeDescriptor is not yet supported
so I filed a feedback, let's hope for the best, as there's no lightweight alternative to generate DataMatrix codes that I could find

AppleWatch Messages URL works hard coded but not with variables

TLDR When I hard code phone numbers into a URL it opens in watch messages correctly, but when I use a variable string with the numbers typed in exactly the same way inside of it, it doesn't.
Example:
NSURL(string: "sms:/open?addresses=8888888888,9999999999,3333333333&body=Test")
Above code works but below code doesn't:
let hardCode = "8888888888,9999999999,3333333333"
NSURL(string: "sms:/open?addresses=\(hardCode)&body=Test")
FULL DETAILS:
I am making a URL from variables to open messages on the Apple Watch with pre-filled contents. I am getting the phone numbers from the contact book and storing them in an array. They are provided in this format:
(###) ###-#### but need to be ##########
I tested the code by hard-coding phone numbers into the URL and it works properly with all contacts and completed body:
if let urlSafeBody = urlSafeBody, url = NSURL(string: "sms:/open?addresses=8888888888,9999999999,3333333333&body=\(urlSafeBody)") {
print("FINAL URL: \(url)")
WKExtension.sharedExtension().openSystemURL(url)
}
But when I build the phone number values programmatically it does not work:
//holds phone numbers without special chars
var tempArray: [String] = []
//if I can access the unformatted numbers
if let recips = saveData["recips"] as? [String] {
//for each number provided
recips.forEach { (person: String) in
//remove all non-numerical digits
//person is now (###) ###-####
let newPerson = person.digitsOnly()
//newPerson is ##########
print(person)
print("->\(newPerson)")
//add formatted number to tempArray
tempArray.append(newPerson)
}
}
//combine all numbers with "," between as a string
let recipString = tempArray.joinWithSeparator(",")
//recipString contains ##########,##########,##########...
extension String {
func digitsOnly() -> String{
let stringArray = self.componentsSeparatedByCharactersInSet(
NSCharacterSet.decimalDigitCharacterSet().invertedSet)
let newString = stringArray.joinWithSeparator("")
return newString
}
}
I then add the "recipString" variable to the NSURL in the below code:
let messageBody = "test"
let urlSafeBody = messageBody.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLHostAllowedCharacterSet())
if let urlSafeBody = urlSafeBody, url = NSURL(string: "sms:/open?addresses=\(recipString)&body=\(urlSafeBody)") {
print("FINAL URL: \(url)")
WKExtension.sharedExtension().openSystemURL(url)
}
The FINAL URL print shows the correct string, but the messages app does not open properly, and shows quick reply menu instead of composed message window. It matches the functioning hard coded number version exactly, but behaves differently.
Totally lost, hope someone can help!
UPDATE 1
Here are the debug prints for both versions of the URL:
Manually declared (not created from recipString but actually declared in the URL string explicitly):
This version works
FINAL URL: sms:/open?addresses=0000000000,1111111111,2222222222,3333333333,4444444444&body=test
Variable created (using recipString):
This version doesn't
FINAL URL: sms:/open?addresses=0000000000,1111111111,2222222222,3333333333,4444444444&body=test
I have also tried applying url encoding to the "recipString" variable by using the below if let:
if let urlSafeRecip = recipString.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLQueryAllowedCharacterSet()) {
if let urlSafeBody = urlSafeBody, url = NSURL(string: "sms:/open?addresses=\(urlSafeRecip)&body=\(urlSafeBody)") {
print("FINAL URL: \(url)")
WKExtension.sharedExtension().openSystemURL(url)
}
}
UPDATE 2
I tested to see if the hardcode version of numbers matches the recipString exactly via this code:
let hardCode = "0000000000,1111111111,2222222222,3333333333,4444444444"
let isEqual = (hardCode == recipString)
if isEqual {
print("hardCode matches recipString")
}
else {
print("hardCode does not match recipString")
}
Debug prints:
hardCode matches recipString
UPDATE 3
I have confirmed that:
When a URL is made with hard coded numbers vs. numbers that I make from variables, checking == between them returns true.
In every test I can do between the two version of the url, it matches.
NOTES AFTER CORRECT ANSWER FOUND:
This type of URL formatting will ONLY work with multiple addresses in the URL. If you do not have multiple addresses you will need to do the following, which is undocumented but none-the-less works. I found this by bashing my face on the keyboard for hours, so if it helps you an upvote is deserved :)
follow the answer marked below, and then use this type of logic check before making the URL in the doItButton() function he mentioned:
func setupAndSendMsg(saveData: NSDictionary) {
if let urlSafeBody = createBody(saveData) {
let theNumbers = createNumbers(saveData).componentsSeparatedByString(",")
print(theNumbers.count-1)
if theNumbers.count-1 > 0 {
if let url = NSURL(string: "sms:/open?addresses=\(createNumbers(saveData))&body=\(urlSafeBody)") {
print(url)
WKExtension.sharedExtension().openSystemURL(url)
}
} else {
if let url = NSURL(string: "sms:/open?address=\(createNumbers(saveData)),&body=\(urlSafeBody)") {
print(url)
WKExtension.sharedExtension().openSystemURL(url)
}
}
}
}
My guess is that it is not the acctual openSystemUrl call that is the problem. I believe there must be something with the code that is building the number string programmatically.
The code bellow is a simplified version of all the code you have posted. I have confirmed that it is working on my Apple Watch. It opens the Messages app with pre-populated numbers & body text.
Take one more look at your code and see if there is something your missing. If you can't find anything, just delete the code and re-write it, probably will be faster then spotting the weird issue.
Once again the code bellow is confirmed working as expected, so you should be able to get it to work. (or just copy & paste my code) :)
class InterfaceController: WKInterfaceController {
#IBAction func doItButton() {
if let urlSafeBody = createBody() {
if let url = NSURL(string: "sms:/open?addresses=\(createNumbers())&body=\(urlSafeBody)") {
print(url)
WKExtension.sharedExtension().openSystemURL(url)
}
}
}
private func createBody() -> String? {
let messageBody = "hello test message"
return messageBody.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLHostAllowedCharacterSet())
}
private func createNumbers() -> String {
let numbers = ["(111) 222-3333", "(444) 555-6666"]
var tempArray: [String] = []
numbers.forEach { (number: String) in
tempArray.append(number.digitsOnly())
}
return tempArray.joinWithSeparator(",")
}
}
extension String {
func digitsOnly() -> String{
let stringArray = self.componentsSeparatedByCharactersInSet(
NSCharacterSet.decimalDigitCharacterSet().invertedSet)
let newString = stringArray.joinWithSeparator("")
return newString
}
}
With above said I would recommend against using undocumented Apple features for anything you plan on putting on the App Store for the reasons already mentioned in comments.