Swift protocol for string interpolation
You need to implement the Printable
protocol:
This protocol should be adopted by types that wish to customize their
textual representation. This textual representation is used when
objects are written to anOutputStreamType
.
protocol Printable {
var description: String { get }
}
There's also the DebugPrintable
protocol when it's only for debugging purposes:
This protocol should be adopted by types that wish to customize
their textual representation used for debugging purposes. This
textual representation is used when objects are written to an
OutputStreamType
.
protocol DebugPrintable {
var debugDescription: String { get }
}
Documentation (Thanks @MartinR)
Note: As @Antonio and @MartinR mentioned in the comments, this doesn't work in the playground (as of Xcode6 GM anyway); that's a known bug. It does work in compiled apps.
From the Xcode6 GM Release Notes:
In Playgrounds, println() ignores the Printable conformance of
user-defined types. (16562388)
As of Swift 2.0 Printable
has now become CustomStringConvertible
. Everything stays the same as before, you still need to implement
var description: String { get }
But now its called CustomStringConvertible. And debug is CustomDebugStringConvertible
swift default string interpolation for object
The property description
you have seen belongs to the protocol CustomStringConvertible and is used when you want to convert an object to a string representation. So all you need to do is to conform to the protocol and description
will be used in your tests
struct Blah: CustomStringConvertible {
let value: String
init(_ value: String) {
self.value = value
}
var description: String {
value
}
}
Difference between Swift String Interpolation Prints
For such a question, we should take a look at print(_:separator:terminator:)
parameters:
1) items
: is a variadic parameter of type Any
, which means that you can pass zero or more items to it. Example:
print() // passing nothing
print("Hello") // passing single item (String)
print(101, 40.45, false, ["Hi", "Greetings"]) // passing multiple items
2) separator
: the string to print between each item (as mentioned in its documentation). Example:
print(101, 40.45, false, ["Hi", "Greetings"], separator: " <=> ")
// 101<=>40.45<=>false<=>["Hi", "Greetings"]
3) terminator
: the string to print after all items have been printed (as mentioned in its documentation). Example:
print(101, 40.45, false, ["Hi", "Greetings"], terminator: " ==>")
// 101 40.45 false ["Hi", "Greetings"] ==>
Back to your cases:
First, keep in mind that for all of your three cases you are passing only items
parameter; It is valid -for sure- because separator
and terminator
have default values as " "
and \n
.
Now, for the first and third print statements
print("Values is: \(value)")
print("Values is: " + value)
what happens is: actually you are dealing with Strings
, it is not about the print
itself. You can do interpolation in strings as well as using the +
for concatenating strings without the print
:
// interpolation:
let name = "Jack"
let greetingMessage = "Greetings, \(name)"
print(greetingMessage) // => Greetings, Jack
// concatenating:
let concatenated = "Greetings" + ", " + "Sara"
print(concatenated) // => "Greetings" + ", " + "Sara"
Which means that you are passing a single String
item, regardless of doing interpolation or concatenation for it.
You could also check The +
function implementation in Swift. Basically, it is an append
!
The second print
statement:
print("Values is:", value)
What happens here is: you are passing two items; According to the default value for separator
, the output is:
Values is: 5
As:
Values is: 5
^ ^^
| ||__ item #2
item #1 |
|
default separator (" ")
Protocol function implementation without actually conforming to a protocol
You must conform to CustomStringConvertible
if you want string interpolation to use your description
property.
You use string interpolation in Swift like this:
"Here's my linked list: \(linkedList)"
The compiler basically turns that into this:
String(stringInterpolation:
String(stringInterpolationSegment: "Here's my linked list: "),
String(stringInterpolationSegment: linkedList),
String(stringInterpolationSegment: ""))
There's a generic version of String(stringInterpolationSegment:)
defined like this:
public init<T>(stringInterpolationSegment expr: T) {
self = String(describing: expr)
}
String(describing: )
is defined like this:
public init<Subject>(describing instance: Subject) {
self.init()
_print_unlocked(instance, &self)
}
_print_unlocked
is defined like this:
internal func _print_unlocked<T, TargetStream : TextOutputStream>(
_ value: T, _ target: inout TargetStream
) {
// Optional has no representation suitable for display; therefore,
// values of optional type should be printed as a debug
// string. Check for Optional first, before checking protocol
// conformance below, because an Optional value is convertible to a
// protocol if its wrapped type conforms to that protocol.
if _isOptional(type(of: value)) {
let debugPrintable = value as! CustomDebugStringConvertible
debugPrintable.debugDescription.write(to: &target)
return
}
if case let streamableObject as TextOutputStreamable = value {
streamableObject.write(to: &target)
return
}
if case let printableObject as CustomStringConvertible = value {
printableObject.description.write(to: &target)
return
}
if case let debugPrintableObject as CustomDebugStringConvertible = value {
debugPrintableObject.debugDescription.write(to: &target)
return
}
let mirror = Mirror(reflecting: value)
_adHocPrint_unlocked(value, mirror, &target, isDebugPrint: false)
}
Notice that _print_unlocked
only calls the object's description
method if the object conforms to CustomStringConvertible
.
If your object doesn't conform to CustomStringConvertible
or one of the other protocols used in _print_unlocked
, then _print_unlocked
creates a Mirror
for your object, which ends up just printing the object's type (e.g. MyProject.LinkedList
) and nothing else.
String property returning different values when used directly or in string interpolation
I feel like this is somewhat similar to the solution of this question of mine. In that question, there is also two almost identical properties - one optional, the other non-optional. And I experienced a similar situation where Swift can't figure out which property I want.
Your titleString
in SomeClass
is not overriding the titleString
property in the protocol. This is reflected in Xcode's suggestions:
You can access both properties like this:
someObject.titleString as String // accesses the one in SomeClass
someObject.titleString as String? // accesses the one in the protocol
My point here is that the type of the expression matters. If the type of expression swift expects is String
, then it resolves to the one in SomeClass
. If the expected type of the expression is String?
, then it evaluates to the one in the protocol.
This explains why setting the label's text without string interpolation will call the property in the protocol (label.text
is String?
, so it expects a String?
) and why using string interpolation will call the property in SomeClass
(String interpolation expects a non-optional).
What are the supported Swift String format specifiers?
The format specifiers for String
formatting in Swift are the same as those in Objective-C NSString
format, itself identical to those for CFString
format and are buried deep in the archives of Apple Documentation (same content for both pages, both originally from year 2002 or older):
- https://developer.apple.com/library/archive/documentation/CoreFoundation/Conceptual/CFStrings/formatSpecifiers.html
- https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Strings/Articles/formatSpecifiers.html
But this documentation page itself is incomplete, for instance the flags, the precision specifiers and the width specifiers aren't mentioned. Actually, it claims to follow IEEE printf specifications (Issue 6, 2004 Edition), itself aligned with the ISO C standard. So those specifiers should be identical to what we have with C printf
, with the addition of the %@
specifier for Objective-C objects, and the addition of the poorly documented %D
, %U
, %O
specifiers and q
length modifier.
Specifiers
Each conversion specification is introduced by the '%' character or by the character sequence "%n$".
n
is the index of the parameter, like in:
String(format: "%2$@ %1$@", "world", "Hello")
Format Specifiers
%@ Objective-C object, printed as the string returned by descriptionWithLocale: if available, or description otherwise.
Actually, you may also use some Swift types, but they must be defined inside the standard library in order to conform to the CVarArg protocol, and I believe they need to support bridging to Objective-C objects: https://developer.apple.com/documentation/foundation/object_runtime/classes_bridged_to_swift_standard_library_value_types.
String(format: "%@", ["Hello", "world"])
%% '%' character.
String(format: "100%% %@", true.description)
%d, %i Signed 32-bit integer (int).
String(format: "from %d to %d", Int32.min, Int32.max)
%u, %U, %D Unsigned 32-bit integer (unsigned int).
String(format: "from %u to %u", UInt32.min, UInt32.max)
%x Unsigned 32-bit integer (unsigned int), printed in hexadecimal using the digits 0–9 and lowercase a–f.
String(format: "from %x to %x", UInt32.min, UInt32.max)
%X Unsigned 32-bit integer (unsigned int), printed in hexadecimal using the digits 0–9 and uppercase A–F.
String(format: "from %X to %X", UInt32.min, UInt32.max)
%o, %O Unsigned 32-bit integer (unsigned int), printed in octal.
String(format: "from %o to %o", UInt32.min, UInt32.max)
%f 64-bit floating-point number (double), printed in decimal notation. Produces "inf", "infinity", or "nan".
String(format: "from %f to %f", Double.leastNonzeroMagnitude, Double.greatestFiniteMagnitude)
%F 64-bit floating-point number (double), printed in decimal notation. Produces "INF", "INFINITY", or "NAN".
String(format: "from %F to %F", Double.leastNonzeroMagnitude, Double.greatestFiniteMagnitude)
%e 64-bit floating-point number (double), printed in scientific notation using a lowercase e to introduce the exponent.
String(format: "from %e to %e", Double.leastNonzeroMagnitude, Double.greatestFiniteMagnitude)
%E 64-bit floating-point number (double), printed in scientific notation using an uppercase E to introduce the exponent.
String(format: "from %E to %E", Double.leastNonzeroMagnitude, Double.greatestFiniteMagnitude)
%g 64-bit floating-point number (double), printed in the style of %e if the exponent is less than –4 or greater than or equal to the precision, in the style of %f otherwise.
String(format: "from %g to %g", Double.leastNonzeroMagnitude, Double.greatestFiniteMagnitude)
%G 64-bit floating-point number (double), printed in the style of %E if the exponent is less than –4 or greater than or equal to the precision, in the style of %f otherwise.
String(format: "from %G to %G", Double.leastNonzeroMagnitude, Double.greatestFiniteMagnitude)
%c 8-bit unsigned character (unsigned char).
String(format: "from %c to %c", "a".utf8.first!, "z".utf8.first!)
%C 16-bit UTF-16 code unit (unichar).
String(format: "from %C to %C", "爱".utf16.first!, "终".utf16.first!)
%s Null-terminated array of 8-bit unsigned characters.
"Hello world".withCString {
String(format: "%s", $0)
}
%S Null-terminated array of 16-bit UTF-16 code units.
"Hello world".withCString(encodedAs: UTF16.self) {
String(format: "%S", $0)
}
%p Void pointer (void *), printed in hexadecimal with the digits 0–9 and lowercase a–f, with a leading 0x.
var hello = "world"
withUnsafePointer(to: &hello) {
String(format: "%p", $0)
}
%n The argument shall be a pointer to an integer into which is written the number of bytes written to the output so far by this call to one of the fprintf() functions.
The n
format specifier seems unsupported in Swift 4+
%a 64-bit floating-point number (double), printed in scientific notation with a leading 0x and one hexadecimal digit before the decimal point using a lowercase p to introduce the exponent.
String(format: "from %a to %a", Double.leastNonzeroMagnitude, Double.greatestFiniteMagnitude)
%A 64-bit floating-point number (double), printed in scientific notation with a leading 0X and one hexadecimal digit before the decimal point using a uppercase P to introduce the exponent.
String(format: "from %A to %A", Double.leastNonzeroMagnitude, Double.greatestFiniteMagnitude)
Flags
' The integer portion of the result of a
decimal conversion ( %i, %d, %u, %f, %F, %g, or %G ) shall be
formatted with thousands' grouping characters. For other conversions
the behavior is undefined. The non-monetary grouping character is
used.
The '
flag seems unsupported in Swift 4+
- The result of the conversion shall be left-justified within the field. The conversion is right-justified if
this flag is not specified.
String(format: "from %-12f to %-12d.", Double.leastNonzeroMagnitude, Int32.max)
+ The result of a signed conversion shall always begin with a sign ( '+' or '-' ). The conversion shall begin
with a sign only when a negative value is converted if this flag is
not specified.
String(format: "from %+f to %+d", Double.leastNonzeroMagnitude, Int32.max)
<space> If the first character of a signed
conversion is not a sign or if a signed conversion results in no
characters, a <space> shall be prefixed to the result. This means
that if the <space> and '+' flags both appear, the <space> flag
shall be ignored.
String(format: "from % d to % d.", Int32.min, Int32.max)
# Specifies that the value is to be converted to an alternative form. For o conversion, it increases the precision
(if necessary) to force the first digit of the result to be zero. For
x or X conversion specifiers, a non-zero result shall have 0x (or 0X)
prefixed to it. For a, A, e, E, f, F, g , and G conversion specifiers,
the result shall always contain a radix character, even if no digits
follow the radix character. Without this flag, a radix character
appears in the result of these conversions only if a digit follows it.
For g and G conversion specifiers, trailing zeros shall not be removed
from the result as they normally are. For other conversion specifiers,
the behavior is undefined.
String(format: "from %#a to %#x.", Double.leastNonzeroMagnitude, UInt32.max)
0 For d, i, o, u, x, X, a, A, e, E, f, F, g,
and G conversion specifiers, leading zeros (following any indication
of sign or base) are used to pad to the field width; no space padding
is performed. If the '0' and '-' flags both appear, the '0' flag is
ignored. For d, i, o, u, x, and X conversion specifiers, if a
precision is specified, the '0' flag is ignored. If the '0' and '"
flags both appear, the grouping characters are inserted before zero
padding. For other conversions, the behavior is undefined.
String(format: "from %012f to %012d.", Double.leastNonzeroMagnitude, Int32.max)
Width modifiers
If the converted value has fewer bytes than the field width, it shall be padded with spaces by default on the left; it shall be padded on the right if the left-adjustment flag ( '-' ) is given to the field width. The field width takes the form of an asterisk ( '*' ) or a decimal integer.
String(format: "from %12f to %*d.", Double.leastNonzeroMagnitude, 12, Int32.max)
Precision modifiers
An optional precision that gives the minimum number of digits to appear for the d, i, o, u, x, and X conversion specifiers; the number of digits to appear after the radix character for the a, A, e, E, f, and F conversion specifiers; the maximum number of significant digits for the g and G conversion specifiers; or the maximum number of bytes to be printed from a string in the s and S conversion specifiers. The precision takes the form of a period ( '.' ) followed either by an asterisk ( '*' ) or an optional decimal digit string, where a null digit string is treated as zero. If a precision appears with any other conversion specifier, the behavior is undefined.
String(format: "from %.12f to %.*d.", Double.leastNonzeroMagnitude, 12, Int32.max)
Length modifiers
h Length modifier specifying that a following
d, o, u, x, or X conversion specifier applies to a short or unsigned
short argument.
String(format: "from %hd to %hu", CShort.min, CUnsignedShort.max)
hh Length modifier specifying that a following
d, o, u, x, or X conversion specifier applies to a signed char or
unsigned char argument.
String(format: "from %hhd to %hhu", CChar.min, CUnsignedChar.max)
l Length modifier specifying that a following
d, o, u, x, or X conversion specifier applies to a long or unsigned
long argument.
String(format: "from %ld to %lu", CLong.min, CUnsignedLong.max)
ll, q Length modifiers specifying that a
following d, o, u, x, or X conversion specifier applies to a long long
or unsigned long long argument.
String(format: "from %lld to %llu", CLongLong.min, CUnsignedLongLong.max)
L Length modifier specifying that a following
a, A, e, E, f, F, g, or G conversion specifier applies to a long
double argument.
I wasn't able to pass a CLongDouble argument to format
in Swift 4+
z Length modifier specifying that a following
d, o, u, x, or X conversion specifier applies to a size_t.
String(format: "from %zd to %zu", size_t.min, size_t.max)
t Length modifier specifying that a following
d, o, u, x, or X conversion specifier applies to a ptrdiff_t.
String(format: "from %td to %tu", ptrdiff_t.min, ptrdiff_t.max)
j Length modifier specifying that a following
d, o, u, x, or X conversion specifier applies to a intmax_t or
uintmax_t argument.
String(format: "from %jd to %ju", intmax_t.min, uintmax_t.max)
Related Topics
Uiprogressview Progress Update Very Slow Within Alamofire (Async) Call
Ios/Tvos Playground Fails with "Unable to Find Execution Service for Selected Run Destination"
How to Make Swiftui Uiviewrepresentable View Hug Its Content
Programmatically Create an Nsviewcontroller Without an Xib in Swift 3
@Objc Redundancy When Having @Objcmembers Private Dynamic Var
Nested Tabview - Remove Inner Tab Bar iOS 13, Swift Ui
Scntext Alignment Not Working in iOS
How to Place Objects with Hit Test Without Plane Detection
Custom Intialiser on Primitive Types for JSONdecoder
How to Keep a Reference to Another Object in the Parameters of the Class
Swift Enumeration Order and Comparison
Firebase References Undeclared
Refreshing Auth Token with Moya
Swiftui MACos Commands (Menu Bar) and View
Prevent Error "Funk" Sound in Event Monitor Os X
How to Convert Data of Int16 Audio Samples to Array of Float Audio Samples