Skip to content

Commit

Permalink
add gesture recognizer
Browse files Browse the repository at this point in the history
  • Loading branch information
kateinoigakukun committed Jan 23, 2018
1 parent 2779db4 commit bb34d55
Show file tree
Hide file tree
Showing 4 changed files with 65 additions and 20 deletions.
30 changes: 28 additions & 2 deletions Sources/IBLinterCore/InterfaceBuilderNode.swift
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ public extension InterfaceBuilderNode {
public let colorMatched: Bool?
public let device: Device?
public let views: [View]?
public let gestureRecognizers: [GestureRecognizer]?
public let placeholders: [Placeholder]?

static func decode(_ xml: XMLIndexerProtocol) throws -> InterfaceBuilderNode.XibDocument {
Expand All @@ -70,6 +71,7 @@ public extension InterfaceBuilderNode {
colorMatched: xml.attributeValue(of: "colorMatched"),
device: xml.byKey("device").flatMap(decodeValue),
views: xml.byKey("objects")?.childrenNode.flatMap(decodeValue),
gestureRecognizers: xml.byKey("objects")?.childrenNode.flatMap(decodeValue),
placeholders: xml.byKey("objects")?.byKey("placeholder")?.allElements.flatMap(decodeValue)
)
}
Expand All @@ -92,11 +94,13 @@ public extension InterfaceBuilderNode {
public struct Scene: XMLDecodable {
public let id: String
public let viewController: ViewController?
public let gestureRecognizers: [GestureRecognizer]?

static func decode(_ xml: XMLIndexerProtocol) throws -> InterfaceBuilderNode.Scene {
return Scene.init(
id: try xml.attributeValue(of: "sceneID"),
viewController: xml.byKey("objects")?.childrenNode.first.flatMap(decodeValue)
id: try xml.attributeValue(of: "sceneID"),
viewController: xml.byKey("objects")?.childrenNode.flatMap(decodeValue).first,
gestureRecognizers: xml.byKey("objects")?.childrenNode.flatMap(decodeValue)
)
}
}
Expand Down Expand Up @@ -143,6 +147,28 @@ public extension InterfaceBuilderNode {
}
}

public struct GestureRecognizer: XMLDecodable {

static let classNames: [String] = [
"tapGestureRecognizer", "pinchGestureRecognizer",
"rotationGestureRecognizer", "swipeGestureRecognizer",
"panGestureRecognizer", "screenEdgePanGestureRecognizer",
"pongPressGestureRecognizer", "gestureRecognizer",
]

public let connections: [InterfaceBuilderNode.View.Connection]?

static func decode(_ xml: XMLIndexerProtocol) throws -> InterfaceBuilderNode.GestureRecognizer {
guard let elementName = xml.elementNode?.name, classNames.contains(elementName) else {
throw Error.elementNotFound
}

return GestureRecognizer.init(
connections: xml.byKey("connections")?.childrenNode.flatMap(decodeValue)
)
}
}

public enum Error: Swift.Error, CustomStringConvertible {
case elementNotFound
case unsupportedViewClass(String)
Expand Down
1 change: 1 addition & 0 deletions Sources/IBLinterCore/InterfaceBuilderView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ extension InterfaceBuilderNode {
case "view": return try .view(decodeValue(xml))
case "placeholder": throw Error.notViewElement
default:
if GestureRecognizer.classNames.contains(elementName) { throw Error.notViewElement }
let error = Error.unsupportedViewClass(elementName)
print(error.description)
return try .unknownView(decodeValue(xml))
Expand Down
50 changes: 34 additions & 16 deletions Sources/IBLinterKit/Rules/OutletConnectionRule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,25 @@ extension Rules {
public private(set) var idToClassName: [String: String] = [:]
public private(set) var classNameToConnection: [String: [InterfaceBuilderNode.View.Connection]] = [:]

public init(view: ViewProtocol) {
mappingIdToClassName(for: view)
mappingClassNameToConnection(for: view)
public init(xibDocument: InterfaceBuilderNode.XibDocument) {
xibDocument.views?.forEach { [weak self] view in
self?.mappingIdToClassName(for: view)
self?.mappingClassNameToConnection(for: view)
self?.mappingGestureRecognizer(for: xibDocument.gestureRecognizers ?? [])
}
}

public init(viewController: ViewControllerProtocol) {
mappingIdToClassName(for: viewController)
mappingClassNameToConnection(for: viewController)
guard let rootView = viewController.rootView else { return }
mappingIdToClassName(for: rootView)
mappingClassNameToConnection(for: rootView)
public init(storyboardDocument: InterfaceBuilderNode.StoryboardDocument) {
storyboardDocument.scenes?.flatMap { $0.viewController }
.forEach { [weak self] viewController in
self?.mappingIdToClassName(for: viewController)
self?.mappingClassNameToConnection(for: viewController)
guard let rootView = viewController.rootView else { return }
self?.mappingIdToClassName(for: rootView)
self?.mappingClassNameToConnection(for: rootView)
}
let gestureRecognizers = storyboardDocument.scenes?.flatMap { $0.gestureRecognizers }.flatMap { $0 } ?? []
mappingGestureRecognizer(for: gestureRecognizers)
}

private func mappingIdToClassName(for viewController: ViewControllerProtocol) {
Expand All @@ -47,6 +55,19 @@ extension Rules {
view.subviews?.forEach(mappingIdToClassName)
}

private func mappingGestureRecognizer(for gestureRecognizers: [InterfaceBuilderNode.GestureRecognizer]) {
gestureRecognizers.flatMap { $0.connections }.flatMap { $0 }
.forEach { [weak self] connection in
switch connection {
case .action(_, let destination, _, _):
guard let className = self?.idToClassName[destination] else { return }
self?.classNameToConnection[className]?.append(connection)
default: return
}
}

}

private func mappingClassNameToConnection(for view: ViewProtocol) {
defer { view.subviews?.forEach(mappingClassNameToConnection) }
guard let connections = view.connections else { return }
Expand Down Expand Up @@ -133,16 +154,13 @@ extension Rules {
}()

public func validate(xib: XibFile, swiftParser: SwiftIBParser) -> [Violation] {
guard let views = xib.document.views else { return [] }
return views.map(Mapper.init(view: ))
.flatMap { self.validate(for: $0, file: xib, swiftParser: swiftParser) }
let mapper = Mapper.init(xibDocument: xib.document)
return validate(for: mapper, file: xib, swiftParser: swiftParser)
}

public func validate(storyboard: StoryboardFile, swiftParser: SwiftIBParser) -> [Violation] {
guard let scenes = storyboard.document.scenes else { return [] }
let viewControllers = scenes.flatMap { $0.viewController }
let mappers = viewControllers.map(Mapper.init(viewController: ))
return mappers.flatMap { self.validate(for: $0, file: storyboard, swiftParser: swiftParser) }
let mapper = Mapper.init(storyboardDocument: storyboard.document)
return validate(for: mapper, file: storyboard, swiftParser: swiftParser)
}

private func validate(for mapper: Mapper, file: FileProtocol, swiftParser: SwiftIBParser) -> [Violation] {
Expand Down
4 changes: 2 additions & 2 deletions Tests/IBLinterKitTest/RuleTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ class RuleTest: XCTestCase {
func testOutletConnection() {
let path = "Tests/IBLinterKitTest/Resources/IBConnectionViewController.storyboard"
let storyboard = StoryboardFile.init(path: path)
guard let viewController = storyboard.document.scenes?.first?.viewController else { return }
let document = storyboard.document

let cache = Rules.OutletConnectionRule.Mapper.init(viewController: viewController)
let cache = Rules.OutletConnectionRule.Mapper.init(storyboardDocument: document)
XCTAssertEqual(cache.idToClassName["Ksm-ni-UfK"], "IBConnectionViewController")
let classNameToConnection: [InterfaceBuilderNode.View.Connection] = [
.outlet(property: "label", destination: "4Kb-9I-U6T", id: "pIv-Ri-ced"),
Expand Down

0 comments on commit bb34d55

Please sign in to comment.