Pro Yearly is on sale from $80 to $50! »

Custom URL Schemeを支える技術

011714704c4a925e542d426d4cdaa4e3?s=47 giginet
July 26, 2018

Custom URL Schemeを支える技術

011714704c4a925e542d426d4cdaa4e3?s=128

giginet

July 26, 2018
Tweet

Transcript

  1. Custom URL Scheme Λࢧ͑Δٕज़ #potatotips 53 @giginet

  2. ୭ • @giginet • ϞόΠϧج൫ @ Cookpad Inc. • iOSDCొஃ͠·͢

    • ৄղFastfile •
  3. None
  4. None
  5. 30Ҏ্ͷURL Scheme

  6. ໰୊఺ • ར༻ՄೳͳSchemeͷҰཡੑ͕௿͍ • URLதʹؚ·ΕΔจࣈྻ΍Query Parameterͷ ύʔε͕໘౗

  7. None
  8. pokedex://pokemons/25

  9. let url = URL(string: "pokedex://pokemons/25")! let components = url.pathComponents if

    url.scheme == "pokedex" { if url.host == "pokemons" { if components.count == 2, let pokedexID: Int = Int(components[1]) { presentPokemonDetailViewController(of: pokedexID) } else if ( ... ) { } else { // ... } } }
  10. let url = URL(string: "pokedex://pokemons/25")! let components = url.pathComponents if

    url.scheme == "pokedex" { if url.host == "pokemons" { if components.count == 2, let pokedexID: Int = Int(components[1]) { presentPokemonDetailViewController(of: pokedexID) } else if ( ... ) { } else { // ... } } }
  11. giginet/Crossroad • URLSchemeΛϧʔςΟϯάͯ͠URL͔Β؆୯ ʹ஋Λऔಘ͢Δ΍ͭ

  12. import Crossroad class AppDelegate: UIApplicationDelegate { let router: DefaultRouter =

    { let router = DefaultRouter(scheme: "scheme") router.register([ ("scheme://search", { _ in return true }), // ... ]) return router }() func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey: Any]) -> Bool { return router.openIfPossible(url, options: options) } }
  13. let router = DefaultRouter(scheme: "pokedex") router.register([ ("pokedex://pokemons", { context in

    let type: Type? = context.parameter(for: "type") presentPokedexListViewController(for: type) return true }), ("pokedex://pokemons/:pokedexID", { context in guard let pokedexID: Int = try? context.arguments(for: "pokedexID") else { return false } guard let pokemon = Pokedex.find(by: pokedexID) else { return false } presentPokemonDetailViewController(of: pokedexID) return true }), ]) router.openIfPossible(url)
  14. let router = DefaultRouter(scheme: "pokedex") router.register([ ("pokedex://pokemons", { context in

    let type: Type? = context.parameter(for: "type") presentPokedexListViewController(for: type) return true }), ("pokedex://pokemons/:pokedexID", { context in guard let pokedexID: Int = try? context.arguments(for: "pokedexID") else { return false } guard let pokemon = Pokedex.find(by: pokedexID) else { return false } presentPokemonDetailViewController(of: pokedexID) return true }), ]) router.openIfPossible(url) pokedex://pokemons/25 25
  15. public protocol Extractable { static func extract(from: String) -> Self?

    } extension Int: Extractable { public static func extract(from string: String) -> Int? { return Int(string) } } let pokedexID: Int? = try? context.arguments(for: "pokedexID")
  16. pokedex://pokemons?type=fire

  17. enum Type: String, Extractable { case normal // ϊʔϚϧλΠϓ case

    fire // ΄ͷ͓λΠϓ case water // ΈͣλΠϓ case grass // ͘͞λΠϓ // ... }
  18. public extension RawRepresentable where Self: Extractable, Self.RawValue == String {

    public static func extract(from string: String) -> Self? { return Self(rawValue: string) } }
  19. // match pokedex://pokemons?type=fire ("pokedex://pokemons", { context in let type: Type?

    = context.parameters(for: "type") // ϙέϞϯҰཡը໘Λදࣔ͢Δ presentPokemonListViewController(of: type) return true })
  20. pokedex://pokemons?type=water,grass

  21. // pokedex://pokemons?types=water,grass let types: [Type]? = context.parameters(for: "types") // [.water,

    .grass]
  22. extension Array: Extractable where Array.Element: Extractable { static func extract(from

    string: String) -> [Element]? { let components = string.split(separator: ",") return components .map { String($0) } .compactMap(Element.extract(from:)) } }
  23. Dynamic Member Lookup

  24. @DynamicMemberLookup • ಈతͳϓϩύςΟੜ੒͕ՄೳʹͳΔ౶ҥߏจ • Swift 4.2͔Βར༻Մೳ

  25. @dynamicMemberLookup struct Container { let values: [String: Any] subscript<T>(dynamicMember member:

    String) -> T? { if let value = values[member] { return value as? T } } } let container = Container(values: ["name": "Pikachu"]) let name: String = container.name // Pikachu
  26. // match pokedex://pokemons/:pokedexID // before let pokedexID: Int? = try?

    context.arguments(for: "pokedexID") // after let pokedexID: Int? = context.arguments.pokedexID
  27. @dynamicMemberLookup public struct ArgumentContainer { private let arguments: [String: String]

    public func fetch<T: Extractable>(for key: String) throws -> T { if let argument = arguments[key] { if let value = T.extract(from: argument) { return value } } throw Error.parsingArgumentFailed } public subscript<T: Extractable>(dynamicMember member: String) -> T? { return try? fetch(for: member) } }
  28. https://techlife.cookpad.com/entry/ 2018/06/01/113000

  29. ࠷ۙ

  30. None
  31. ·ͱΊ • େن໛ͳURL Schemeʹ͸Crossroadศར • Swift 4.2ͷ@DynamicMemberLookup • OSS΍ͬͯΔͱϩΰ࡞ͬͯ͘ΕΔਓ͕͍ͯ༏͠ ͍ੈք

    • ⭐͍ͩ͘͞
  32. ͝ਗ਼ௌ ͋Γ͕ͱ͏͍͟͝·ͨ͠