2

Overriding the description variable of a custom class:

override var description : String {
        let mirrored_object = Mirror(reflecting: self)
        let str:NSMutableString = NSMutableString()
        for (index, attr) in mirrored_object.children.enumerated() {
            if let property_name = attr.label as String! {
                //str.append(" Attr \(index): \(property_name) = \(attr.value)\n")
                str.append("\t\(property_name) = \t\t\(attr.value)\n")
            }
        }
        return str as String
    }

Produces output that looks like:

userID =        Optional(0)
username =          Optional("testuser")
email =         Optional("test@gmail.com")

Is there a way to set tabs in the output so that the attribute values line up nicely like this?

userID =        Optional(0)
username =      Optional("testuser")
email =         Optional("test@gmail.com")

Also, is there any way to get rid of or shorten the "Optional" part and just show the value?

GoldenJoe
  • 7,874
  • 7
  • 53
  • 92
  • 1
    For getting rid of the optional part, use null coalescing operator to avoid needing to unwrap it. So change \(attr.value) to \(attr.value ?? "") – totiDev Apr 07 '17 at 07:24

2 Answers2

1

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
Community
  • 1
  • 1
thm
  • 1,217
  • 10
  • 12
  • The unwrap solution in that link didn't work for me, but this answers the main question of formatting my description. – GoldenJoe Apr 10 '17 at 04:47
  • 1
    @GoldenJoe I added an answer myself to the question I linked to and a second update here, does this work for you now? – thm Apr 10 '17 at 12:35
  • Awesome. Thanks so much! I would add that you must subclass NSObject and override description for this to work. Since I have your ear I'll point you to my next question: http://stackoverflow.com/questions/43336724/how-can-i-nest-debug-output-in-swift – GoldenJoe Apr 11 '17 at 04:22
  • 1
    @GoldenJoe Either that or you make whatever you want to print `CustomStringConvertible`, like `struct Foo` above. – thm Apr 11 '17 at 10:57
1

Try use this method. It first calculates the max length of the property, and then uses that to pad the property names:

let maxPropertyLength: Int = mirrored_object.children.map { Int($0.label?.characters.count ?? 0) }.max() ?? 0
for attr in mirrored_object.children {
    if let propertyName = attr.label {
        str.append("\(propertyName.padding(toLength: maxPropertyLength + 2, withPad: " ", startingAt: 0)) = \(attr.value)\n")
    }
}
return str as String
totiDev
  • 5,189
  • 3
  • 30
  • 30