mirror of
https://github.com/nicklockwood/SwiftFormat.git
synced 2026-05-17 10:30:35 +00:00
267 lines
9.2 KiB
Swift
Executable File
267 lines
9.2 KiB
Swift
Executable File
// Copyright © 2017 Schibsted. All rights reserved.
|
|
|
|
import Layout
|
|
import ObjectiveC
|
|
import UIKit
|
|
|
|
private let validClasses: [String] = {
|
|
var classCount: UInt32 = 0
|
|
let classes = objc_copyClassList(&classCount)
|
|
var names = [String]()
|
|
for cls in UnsafeBufferPointer(start: classes, count: Int(classCount)) {
|
|
if class_getSuperclass(cls) != nil,
|
|
class_conformsToProtocol(cls, NSObjectProtocol.self),
|
|
cls.isSubclass(of: UIView.self) || cls.isSubclass(of: UIViewController.self)
|
|
{
|
|
let name = "\(cls)"
|
|
if !name.hasPrefix("_") {
|
|
names.append(name)
|
|
}
|
|
}
|
|
}
|
|
names = names.sorted()
|
|
return names
|
|
}()
|
|
|
|
protocol EditViewControllerDelegate: class {
|
|
func didUpdateClass(_ viewOrControllerClass: NSObject.Type, for node: LayoutNode)
|
|
func didUpdateExpression(_ expression: String, for name: String, in node: LayoutNode)
|
|
}
|
|
|
|
final class EditViewController: UIViewController, UITextFieldDelegate {
|
|
weak var delegate: EditViewControllerDelegate?
|
|
var node: LayoutNode! {
|
|
didSet {
|
|
guard rootNode != nil else { return }
|
|
if oldValue.view.classForCoder != node.view.classForCoder ||
|
|
oldValue.viewController?.classForCoder != node.viewController?.classForCoder
|
|
{
|
|
updateUI()
|
|
} else {
|
|
updateFieldValues()
|
|
}
|
|
}
|
|
}
|
|
|
|
var rootNode: LayoutNode!
|
|
@objc var classField: UITextField?
|
|
var expressionFields = [String: UITextField]()
|
|
|
|
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
|
|
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
|
|
preferredContentSize = CGSize(width: 320, height: 400)
|
|
}
|
|
|
|
@available(*, unavailable)
|
|
required init?(coder _: NSCoder) {
|
|
nil
|
|
}
|
|
|
|
override func viewDidLoad() {
|
|
super.viewDidLoad()
|
|
updateUI()
|
|
}
|
|
|
|
override func viewDidLayoutSubviews() {
|
|
rootNode.update()
|
|
}
|
|
|
|
private func updateFieldValues() {
|
|
for (name, field) in expressionFields {
|
|
field.text = node.expressions[name]
|
|
}
|
|
}
|
|
|
|
private func updateUI() {
|
|
let cls: AnyClass = node.viewController?.classForCoder ?? node.view.classForCoder
|
|
var children = [
|
|
LayoutNode(
|
|
view: UITextField(),
|
|
outlet: #keyPath(classField),
|
|
expressions: [
|
|
"top": "10",
|
|
"left": "10",
|
|
"width": "100% - 20",
|
|
"height": "auto",
|
|
"borderStyle": "roundedRect",
|
|
"autocorrectionType": "no",
|
|
"autocapitalizationType": "none",
|
|
"placeholder": "Class",
|
|
"editingChanged": "didUpdateText",
|
|
"editingDidEnd": "didUpdateClass",
|
|
"text": "\(cls)",
|
|
]
|
|
),
|
|
]
|
|
expressionFields.removeAll()
|
|
func filterType(_ key: String, _ type: RuntimeType) -> String? {
|
|
if !type.isAvailable {
|
|
return nil
|
|
}
|
|
switch type.swiftType {
|
|
case is CGFloat.Type,
|
|
is Double.Type,
|
|
is Float.Type,
|
|
is Int.Type,
|
|
is NSNumber.Type,
|
|
is Bool.Type,
|
|
is String.Type,
|
|
is NSString.Type,
|
|
is NSAttributedString.Type,
|
|
is UIColor.Type,
|
|
is UIImage.Type,
|
|
is UIFont.Type,
|
|
is CGImage.Type,
|
|
is CGColor.Type,
|
|
is [String].Type:
|
|
return key
|
|
default:
|
|
return type.values.isEmpty ? nil : key
|
|
}
|
|
}
|
|
var fieldNames = ["top", "left", "width", "height", "bottom", "right"]
|
|
fieldNames.append(contentsOf: node.viewControllerExpressionTypes.compactMap(filterType).sorted())
|
|
fieldNames.append(contentsOf: node.viewExpressionTypes.compactMap(filterType).sorted {
|
|
switch ($0.hasPrefix("layer."), $1.hasPrefix("layer.")) {
|
|
case (true, true),
|
|
(false, false):
|
|
return $0 < $1
|
|
case (true, false):
|
|
return false
|
|
case (false, true):
|
|
return true
|
|
}
|
|
})
|
|
|
|
let start = CACurrentMediaTime()
|
|
for name in fieldNames {
|
|
children.append(
|
|
LayoutNode(
|
|
view: UILabel(),
|
|
expressions: [
|
|
"top": "previous.bottom + 5",
|
|
"left": "10",
|
|
"width": "100% - 20",
|
|
"height": "auto",
|
|
"text": name,
|
|
"font": "10",
|
|
]
|
|
)
|
|
)
|
|
|
|
let field = UITextField()
|
|
field.borderStyle = .roundedRect
|
|
field.autocorrectionType = .no
|
|
field.autocapitalizationType = .none
|
|
field.text = node.expressions[name]
|
|
expressionFields[name] = field
|
|
|
|
children.append(
|
|
LayoutNode(
|
|
view: field,
|
|
expressions: [
|
|
"top": "previous.bottom",
|
|
"left": "10",
|
|
"width": "100% - 20",
|
|
"height": "auto",
|
|
"borderStyle": "roundedRect",
|
|
"placeholder": name,
|
|
"editingDidEnd": "didUpdateField:",
|
|
]
|
|
)
|
|
)
|
|
}
|
|
|
|
rootNode = LayoutNode(
|
|
view: UIScrollView(),
|
|
expressions: [
|
|
"width": "100%",
|
|
"height": "100%",
|
|
],
|
|
children: [
|
|
LayoutNode(
|
|
view: UIView(),
|
|
expressions: [
|
|
"width": "100%",
|
|
"height": "auto + 10",
|
|
],
|
|
children: children
|
|
),
|
|
]
|
|
)
|
|
print("nodes:", children.count)
|
|
print("creation:", round((CACurrentMediaTime() - start) * 1000))
|
|
|
|
for view in view.subviews {
|
|
view.removeFromSuperview()
|
|
}
|
|
do {
|
|
try rootNode.mount(in: self)
|
|
} catch {
|
|
print("\nError: \(error)\n")
|
|
}
|
|
|
|
print("creation + mount:", round((CACurrentMediaTime() - start) * 1000))
|
|
}
|
|
|
|
@objc func didUpdateText() {
|
|
classField?.backgroundColor = .white
|
|
guard let classField = classField,
|
|
let textRange = classField.textRange(from: classField.beginningOfDocument, to: classField.selectedTextRange?.start ?? classField.endOfDocument),
|
|
let text = classField.text(in: textRange)
|
|
else {
|
|
return
|
|
}
|
|
var match = ""
|
|
for name in validClasses {
|
|
if match.isEmpty || name.count < match.count,
|
|
name.lowercased().hasPrefix(text.lowercased())
|
|
{
|
|
match = name
|
|
}
|
|
}
|
|
if !match.isEmpty {
|
|
let string = NSMutableAttributedString(string: String(match[...text.endIndex]),
|
|
attributes: [NSAttributedStringKey.foregroundColor: UIColor.black])
|
|
string.append(NSMutableAttributedString(string: String(match[text.endIndex...]),
|
|
attributes: [NSAttributedStringKey.foregroundColor: UIColor.lightGray]))
|
|
classField.attributedText = string
|
|
classField.selectedTextRange = classField.textRange(from: textRange.end, to: textRange.end)
|
|
return
|
|
}
|
|
let string = NSAttributedString(string: text,
|
|
attributes: [NSAttributedStringKey.foregroundColor: UIColor.black])
|
|
classField.attributedText = string
|
|
classField.selectedTextRange = classField.textRange(from: textRange.end, to: textRange.end)
|
|
}
|
|
|
|
@objc func didUpdateClass() {
|
|
guard let text = classField?.text else {
|
|
classField?.text = "UIView"
|
|
delegate?.didUpdateClass(UIView.self, for: node)
|
|
return
|
|
}
|
|
classField?.attributedText = NSAttributedString(string: text, attributes: [NSAttributedStringKey.foregroundColor: UIColor.black])
|
|
guard let cls = NSClassFromString(text) as? NSObject.Type,
|
|
cls is UIView.Type || cls is UIViewController.Type
|
|
else {
|
|
classField?.backgroundColor = UIColor(red: 1, green: 0.5, blue: 0.5, alpha: 1)
|
|
return
|
|
}
|
|
delegate?.didUpdateClass(cls, for: node)
|
|
}
|
|
|
|
@objc func didUpdateField(_ textField: UITextField) {
|
|
for (name, field) in expressionFields where field === textField {
|
|
if (node.expressions[name] ?? "") != (field.text ?? "") {
|
|
delegate?.didUpdateExpression(field.text ?? "", for: name, in: node)
|
|
}
|
|
}
|
|
}
|
|
|
|
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
|
|
textField.resignFirstResponder()
|
|
return false
|
|
}
|
|
}
|