Today I want to discuss strings in Swift

Hello everyone. I am trying to reinforce my knowledge of some interesting topics by writing about them./ I like to write as this gives me an introspection into my thought process, allows others to get possibly interested in a topic and learn from me, and allows me to learn from much better people than myself. If I have time, I will try to write those articles here. Some of them might be blind-related and others will be about mainstream programming. Keep in mind that I am not a programming God, I might make errors and feel free to correct me.

What are we talking about today?

Everyone, even the most novice programmer is using strings. They allow us to represent text in our programs, and text is something that drives our everyday life in many ways. We read articles, write messages to our loved ones and make programs, of course. So we could think that strings are rather uninteresting, sugarcoated array of characters, for example C-Style strings are just an array of characters. Even if an underlying implementation is somewhat different there’s a pretty easy way to use strings just like a char array. For example in C# we could do something like

1
2
string test = "Hello, world!";
char test2 = test[2]; // returns 'l' since that's the 3rd character in our string.

That trick works in many languages, including Python. But in Swift, the language used mostly in the world of Apple we need to give this seemingly simple task a little bit more thought. Let me show you:

1
2
3
var a = "ABCDEF"
var b = Array(a)
print(b[2])

It is still a simple task but as you can see, we first need to explicitly convert our string to an array of characters. By the way I recommend you do one more test for yourself. What result will be returned by the following statement? print(type(of: b[2])) This whole mess is because Swift strings are fully Unicode-aware. If you have played some Chinese, Japanese, Russian ETC games and you had to use Locale Emulator you have seen the lack of proper Unicode support in practice. The problem with Unicode though is that some characters, especially emoji might be composed of more than one Unicode scalars. A scalar is a basic unit in Unicode, for example some emoji have skin tones which are represented by a scalar. A skin tone without a thumbs up, for instance does not make much sense, but if we allowed for using a Swift string like a char array, we could, in theory get an unprintable character that makes no sense. What I want to say here is that because Swift makes some simple things harder for us, we get many more more grant, useful processes simplified since the language does a lot on our behalf. However Swift strings are even more powerful than that, and there are many APIs from Apple that allows us to work with them in a variety of interesting ways. By the way here the border between a SwiftString and NSString blurs. I know that my wording is not entirely correct, but it is correct enough for our purposes. If you want to read more, there are websites such as NSHipster that will explain the nuances much better than I ever could.

Get a name of an Emoji

Have you ever wondered how VoiceOver knows all the names of various Emoji you encounter in your day to day iPhone usage? There’s just a database stored somewhere which has all the names of those, and we can get them in our code as well. Let me show you an example.

1
2
3
4
5
var emoji = "☭"
/*1*/let coreFoundationString = NSMutableString(string: emoji) as CFMutableString
/*2*/var range = CFRangeMake(0, CFStringGetLength(coreFoundationString))
/*3*/CFStringTransform(coreFoundationString, &range, kCFStringTransformToUnicodeName, false)
print(coreFoundationString)

This code is pretty long and convoluted as is very much non-swift (Welcome to the world of Apple APIs) but after breaking it down it isn’t that bad. And if you were to do many such operations, you would probably abstract it in a more Swifty way anyway so there’s no problem.

  1. We make an NSMutableString from our swift String and immediately convert it to a CFMutableString type. This is because the methods we use later are the part of CFMutableString type. Don’t worry about any performance implications here, the conversion between the two types is basically free.
  2. We make a CFRange that spans throughout the length of the string. Apple likes to operate on ranges a lot while dealing with strings and you are going to see that a lot if you decide to dig deeper.
  3. Here lies the heart of our quest. The function takes four parameters in this case. The first parameter is the string to be transformed, the second parameter requires a few more words. We pass in the range we created in point 2 but we do something that is very rare in Swift, we pass the pointer to the range, not the range itself, hence the & symbol. The third parameter specifies the actual transformation we desire. In this case, we want to get the Unicode name of the symbol, but there are some more interesting options to choose from, for example, get the latin representation of Cyrillic letters, strip diacritics from strings, ETC. The fourth parameter specifies if we should perform the transformation in reverse (true). I couldn’t find any information online which transformations support reversing, so you need to experiment yourself. The return value is a bool. If it’s true, it means that our transformation succeeded.
  4. We finally print our string. If we see the Xcode’s log we see that we might want to prettify the string a little before using it in production, but the result is OK and it is very easy to make it production ready.

Render HTML in our user interface without WKWebView

HTML is very popular when we deal with data from the Internet. Sometimes we need to render it inline in our user interface and we don’t want to spawn a browser for that. Here a type called NSAttributedString come into play. It is a crazily powerful type that is basically a string, but with additional metadata allowing us to perform some advanced operations with relative ease. Let me show you how we can render HTML inline, useful for example when making labels for elements. This code snippets has some SwiftUI and UIKit code in it but we don’t care about it. The reason I included it is completeness.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
	var text:  String
	func makeUIView(context: Context) -> UILabel {
		let label = UILabel()
		DispatchQueue.main.async {
			/*1*/ if let data = text.data(using: .unicode) {
				/*2*/ if let attrString = try? NSAttributedString(data: data, options: [.documentType: NSAttributedString.DocumentType.html], documentAttributes: nil) {
					label.attributedText = attrString
				}
			}
		}
	
	return label
	}

The code is long but we have only 2 points of interest.

  1. We make a Data object out of our String type. Data is just a byte buffer in the memory. We need this buffer in order to make a formatted string out of it in step 2.
  2. We make our attributed string. The first parameter is the data object we want to use as an input. The second parameter is an array of options controlling the rendering process. Here we set our document type to be HTML, we can very easily render Markdown, for example. Actually we don’t even need the NSAttributedStringfor that. We can use the third parameter to pass any additional attributes we might want, such as the author of the document. I left it atnil` here. As we can see, a relatively simple code can perform an actually useful operation on the string. Another thing is that it scales well, we could very easily make a simple HTML to markdown converter in literally few lin es of code. Would it be good? No but the point still stands.

Control how VoiceOver reads strings

We are still staying in the world of NSAttributedString. There are some attributes which make it possible to somewhat control how the string is spoken by VoiceOver. Let me show you a very simple example, taken from Orion Gomez’s audiogame utilities for Swift repository. let attributedLabel = NSAttributedString(string: text, attributes: [NSAttributedString.Key.accessibilitySpeechQueueAnnouncement: true]) This is how we make queued announcements. But there are many more accessibility attributes of which some I will discuss below.

  • accessibilitySpeechPunctuation: Allows to override default punctuation settings of VoiceOver. Useful when we need the user to know the punctuation, for example when reading some kind of code.
  • accessibilitySpeechPitch: allows to speak a text with a voice that has a higher or lower pitch than our VoiceOver defaults.
  • accessibilityTextHeadingLevel: allows to set a custom heading level for a text. Useful when presenting raw markdown to a user. It preserves heading navigation around the text.

Epilogue

This is my first serious article about programming. It cost me a lot to write it because I am very stressed about the reception. I hope that at least one person will benefit from it.