How to Use Tabs to Evenly Space Out Description Strings in Swift

Is there a way to use tabs to evenly space out description strings in Swift?

I wouldn't use tabs but use padding(...):

var description : String {
let mirrored_object = Mirror(reflecting: self)
let childrenWithLabel = mirrored_object.children.filter { $0.label != nil }
let maxLen = childrenWithLabel.map { Int($0.label!.characters.count) }.max() ?? 0
let lines = childrenWithLabel.map { $0.label!.padding(toLength: maxLen, withPad: " ", startingAt: 0) + " = \($0.value)" }
return lines.joined(separator: "\n")
}

For a struct like

struct Foo: CustomStringConvertible
{
let userID = 42
let username = "Foo"
let verylongpropertyname: String? = "Bar"
}

this produces

userID               = 42
username = Foo
verylongpropertyname = Optional("Bar")

As for the "Optional" part, it's not as easy as totiG suggests because the value you get from the mirror is of type Any. See this question.

Update

I overlooked that you wanted to have a slightly different format.

var description : String {
let mirrored_object = Mirror(reflecting: self)
let childrenWithLabel = mirrored_object.children.filter { $0.label != nil }
let separator = " = "
let firstColumnWidth = (childrenWithLabel.map { Int($0.label!.characters.count) }.max() ?? 0) + separator.characters.count
let lines = childrenWithLabel.map {
($0.label! + separator).padding(toLength: firstColumnWidth, withPad: " ", startingAt: 0) + "\($0.value)"
}
}

produces

userID =               42
username = Foo
verylongpropertyname = Optional("Bar")

Update 2

To get rid of the "Optional" thing, please see my answer here.

If you use (from said answer) unwrap() or unwrapUsingProtocol() in description like this:

var description : String {
let mirrored_object = Mirror(reflecting: self)
let childrenWithLabel = mirrored_object.children.filter { $0.label != nil }
let separator = " = "
let firstColumnWidth = (childrenWithLabel.map { Int($0.label!.characters.count) }.max() ?? 0) + separator.characters.count
let lines = childrenWithLabel.map {
($0.label! + separator).padding(toLength: firstColumnWidth, withPad: " ", startingAt: 0) + "\(unwrap($0.value))"
}
return lines.joined(separator: "\n")
}

this will produce

userID =               42
username = Foo
verylongpropertyname = Bar

How can I nest debug output in Swift?

I'd do that with a recursive function with an accumulator parameter for the indentation. It defaults to no indentation and is increased by the first column's width on each recursive call:

func describe<T>(_ x: T, indent: String = "") -> String
{
let mirror = Mirror(reflecting: x)
guard !mirror.children.isEmpty else { return x is String ? "\"\(x)\"" : "\(x)" }

switch mirror.displayStyle! {
case .tuple:
let descriptions = mirror.children.map { describe(unwrap($0.value), indent: indent) }
return "(" + descriptions.joined(separator: ",\n\(indent)") + ")"
case .collection:
let descriptions = mirror.children.map { describe(unwrap($0.value), indent: indent) }
return "[" + descriptions.joined(separator: ",\n\(indent)") + "]"
case .dictionary:
let descriptions = mirror.children.map { (child: Mirror.Child) -> String in
let entryMirrors = Array(Mirror(reflecting: unwrap(child.value)).children)
return describe(unwrap(entryMirrors[0].value), indent: indent) + ": "
+ describe(unwrap(entryMirrors[1].value))
}
return "[" + descriptions.joined(separator: ",\n\(indent)") + "]"
case .set:
let descriptions = mirror.children.map { describe(unwrap($0.value), indent: indent) }
return "Set(" + descriptions.joined(separator: ",\n\(indent)") + ")"
default:
let childrenWithLabel = mirror.children.filter { $0.label != nil }
let separator = " = "
let firstColumnWidth = (childrenWithLabel.map { Int($0.label!.characters.count) }.max() ?? 0)
+ separator.characters.count
let subindent = indent + String(repeating: " ", count: firstColumnWidth)

let lines = childrenWithLabel.map {
indent
+ ($0.label! + separator).padding(toLength: firstColumnWidth, withPad: " ", startingAt: 0)
+ describe(unwrap($0.value), indent: subindent)
}
return (["\(mirror.subjectType)"] + lines).joined(separator: "\n")
}
}

This function uses the unwrap(_:) function from my answer to another question

func unwrap<T>(_ any: T) -> Any
{
let mirror = Mirror(reflecting: any)
guard mirror.displayStyle == .optional, let first = mirror.children.first else {
return any
}
return first.value
}

When using describe(_:) like this (I made MyClass a struct so I can use the memberwise initializer):

struct MyClass: CustomStringConvertible
{
let a_number : Int?
let a_string : String?
let an_array_of_strings : Array<String>?
let an_array_of_objs : Array<Any>?

var description: String { return describe(self) }
}

print(MyClass(a_number: 4, a_string: "hello",
an_array_of_strings: ["str1", "str2", "str3"],
an_array_of_objs: [
MyClass(a_number: 5, a_string: "world",
an_array_of_strings: nil, an_array_of_objs: nil)]))

then the output is

MyClass
a_number = 4
a_string = "hello"
an_array_of_strings = ["str1",
"str2",
"str3"]
an_array_of_objs = [MyClass
a_number = 5
a_string = "world"
an_array_of_strings = nil
an_array_of_objs = nil]

Please note that this is only tested with your specific example and some simple additions. I am also not happy about the forced unwrap of mirror.displayStyle but in my shallow testing this only ever happened when mirror.children is empty, which is covered by the preceding guard. If anybody has investigated this more closely, I'd love a comment. I haven't found anything in the documentation of Mirror.

And just like in my answer to your related question I mixed up where the = is supposed to be. Just the other way round this time, duh! :)

Is there any proper way to fill equally labels in stack view?

Solved my problem by putting label into the view container.

Generating a table with columns of equal width (viewed on the console in Xcode)

Try this:

for i in 0...11 {
let month = (months[i] as NSString).UTF8String
println(String(format:"%-10s %10d %10d %10d", month,
rainfall[i], raindays[i], water_deficit[i]))
}

For details un format and format specifiers see here and here.

In let month = (months[i] as NSString).UTF8String I am applying a conversion from String to C String: it is pretty easy to specify a length in the format specifier of a C String, but I don't know how to do it for a String (my guess is that it is not possible).

How can I indent multiple lines in Xcode?

The keyboard shortcuts are +] for indent and +[ for un-indent.

  • In Xcode's preferences window, click the Key Bindings toolbar button. The Key Bindings section is where you customize keyboard shortcuts.

Split a String into an array in Swift?

The Swift way is to use the global split function, like so:

var fullName = "First Last"
var fullNameArr = split(fullName) {$0 == " "}
var firstName: String = fullNameArr[0]
var lastName: String? = fullNameArr.count > 1 ? fullNameArr[1] : nil

with Swift 2

In Swift 2 the use of split becomes a bit more complicated due to the introduction of the internal CharacterView type. This means that String no longer adopts the SequenceType or CollectionType protocols and you must instead use the .characters property to access a CharacterView type representation of a String instance. (Note: CharacterView does adopt SequenceType and CollectionType protocols).

let fullName = "First Last"
let fullNameArr = fullName.characters.split{$0 == " "}.map(String.init)
// or simply:
// let fullNameArr = fullName.characters.split{" "}.map(String.init)

fullNameArr[0] // First
fullNameArr[1] // Last


Related Topics



Leave a reply



Submit