Andrii Chernenko
Andrii Chernenko

Reputation: 10194

Swift Protocols: hiding some properties

I would like to create a trimmedText property for UITextView and UITextField. Here is what I did:

protocol TrimmedTextSupporting: class {
  var _text: String? { get }
  var trimmedText: String { get }
}

extension TrimmedTextSupporting {
  var trimmedText: String {
    let text = self._text ?? ""
    return text.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet())
  }
}

extension UITextField: TrimmedTextSupporting {

  var _text: String? {
    return self.text
  }
}

extension UITextView: TrimmedTextSupporting {

  var _text: String? {
    return self.text
  }
}

I need _text property because text is declared as String? in UITextField and as String! in UITextView (whyyyy?! >_<). Now I would like to hide this property to avoid cluttering the API.

Here's what I tried:

1) Marking it as private. The compiler doesn't allow this: 'private' modifier cannot be used in protocols

2) Separating it into a private protocol:

private protocol TextExposing {
  var _text: String? { get }
}

extension UITextField: TextExposing {
  var _text: String? {
    return self.text
  }
}

extension UITextView: TextExposing {
  var _text: String? {
    return self.text
  }
}

///////

protocol TrimmedTextSupporting: class {
  var trimmedText: String { get }
}

extension UITextField: TrimmedTextSupporting {}

extension UITextView: TrimmedTextSupporting {}

extension TrimmedTextSupporting where Self: TextExposing {

  // compiler error
  var trimmedText: String {
    let text = self._text ?? ""
    return text.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet())
  }
}

But compiler complains again: Property 'trimmedText' must be declared internal because it matches a requirement in internal protocol 'TrimmedTextSupporting'

I am out of ideas.

Upvotes: 6

Views: 3221

Answers (3)

Archie
Archie

Reputation: 150

Or you can let trimmedText is not required, set to optional.

(if you wanna use optional in Swift, you should let protocol to be @objc)

@objc protocol TrimmedTextSupporting: class {
     optional var trimmedText: String { get }
}

Upvotes: -1

Mike Pollard
Mike Pollard

Reputation: 10195

I'd be inclined to go with:

protocol TrimmedTextSupporting: class {
    var trimmedText: String { get }
}

extension TrimmedTextSupporting {

    private func trimText(text: String) -> String {
        return text.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet())
    }
}

extension UITextField: TrimmedTextSupporting {

    var trimmedText: String {
        return trimText(text ?? "")
    }
}

extension UITextView: TrimmedTextSupporting {

    var trimmedText: String {
        return trimText(text ?? "")
    }
}

So you've avoided duplicating the hard work by doing it in a shared and private function, and the extensions on UITextField and UITextView do the minimum they need to do.

Upvotes: 4

JAL
JAL

Reputation: 42449

You declare TrimmedTextSupporting as an internal Protocol. If you don't want to declare trimmedText as internal, declare TrimmedTextSupporting as a private protocol:

private protocol TrimmedTextSupporting: class {
    var trimmedText: String { get }
}

This compiles fine.

let textView = UITextView()
textView.text = "hello  "
print(textView.trimmedText) // "hello"

let textField = UITextField()
textField.text = "  world "
print(textField.trimmedText) // "world"

Upvotes: 0

Related Questions