NavigationLinkを使う

import SwiftUI
    
struct ContentView: View {
    var body: some View {
        NavigationView {
                NavigationLink(destination: TestView()) {
                    Text("Go Test View")
                }
            .navigationBarTitle("Top View")
        }
        Text("Hello, world!")
            .padding()
    }
}

struct TestView: View {
    var body: some View {
        Text("Test View")
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

SwiftUIでチェック付きのListViewを作る

SwiftUIでチェック付きのListViewを作るのが大変でした。SwiftUI beta4 までOKだったのが Beta5でNGになったので、 検索で見つけたサンプルコードがビルドエラーに! 色々やって以下のコードに落ち着きました。 もっといい方法がありそうなんっだけど。なお、チェック部分は手抜きで文字列です。

import SwiftUI
 
var demoData = [Person(name: "Yamada Taro"), Person(name: "Aoki Ichido"), Person(name: "Sato Koji"), Person(name: "Kojima Masao")]
 
struct PersonView: View {
    @State var selectKeeper = Set<UUID>()
    
    var body: some View {
        VStack {
            NavigationView {
                List {
                    ForEach(demoData) { person in
                        SelectableRow(person: person, selectedItems: self.$selectKeeper)
                    }
                    .navigationBarTitle(Text("Selection Demo \(selectKeeper.count)"))
                }
            }
        } // VStack
    }
}
 
struct Person: Identifiable, Hashable {
    let id = UUID()
    let name: String
}
 
struct SelectableRow: View {
    var person: Person
 
    @Binding var selectedItems: Set<UUID>
    var isSelected: Bool {
        selectedItems.contains(person.id)
    }
    var isSelectedStr: String {
        get {
            if selectedItems.contains(person.id) {
                return "Yes"
            } else {
                return "No"
            }
        }
    }
 
    var body: some View {
        HStack {
            Button(action: {
                if(self.isSelected){
                    self.selectedItems.remove(self.person.id)
                }
                else{
                    self.selectedItems.insert(self.person.id)
                }
            }) {
                Text("\(isSelectedStr)")
            }
            Text(self.person.name)
        }
 
    }
}

Swiftで正規表現と文字列置換

Swiftで正規表現で特定のパターンを見つけて文字列置換をしてみました。 StringとNSString(前からあるやつ)があって使い分けが必要なのと、文字が何文字目かでマッチした文字を取り出すみたい。

func myMethod(_ inStr: String?) -> String? {
    guard let html = inStr else {return nil}
    let html_ns = html as NSString
    var html_result = html
    let regExp = try? NSRegularExpression(
        pattern: ":([^_]+)_([^_]+)_([^:]*):",
        options: NSRegularExpression.Options())
    guard let matches = regExp?.matches(in: html,
        options: NSRegularExpression.MatchingOptions(),
        range: NSMakeRange(0, html_ns.length)) else {return nil}
    for m in matches {
        let command = html_ns.substring(with: m.range(at: 1))
        let param = html_ns.substring(with: m.range(at: 2))
        let text = html_ns.substring(with: m.range(at: 3))
        let command_esc = NSRegularExpression.escapedPattern(for: command)
        let param_esc = NSRegularExpression.escapedPattern(for: param)
        let text_esc = NSRegularExpression.escapedPattern(for: text)
        switch command {
            case "page":
                    html_result = html_result.replacingOccurrences(
                    of: ":\(command_esc)_\(param_esc)_\(text_esc):",
                    with: "<button onclick=\"webkit.messageHandlers.Page.postMessage('\(param_esc)')\">\(text)</button>",
                    options: .regularExpression,
                    range: html_result.range(of: html_result))
            default:
                print(“not implemented")
        } // switch
    } // for
    return html_result
} // func

上記の関数で 「バナナの説明は:page_123_バナナの説明:をクリック」という文字列があると バナナの説明は

<button onclick="webkit.messageHandlers.Page.postMessage(‘123')">バナナの説明</button>をクリック

という風に置き換えます。

SwiftでXMLをパースする。ありがとうyahoojapan

XMLパーサーは、極論すると、これで終わる。簡単!

github.com

こんな感じ。

import SwiftyXMLParser

if let path: String = Bundle.main.path(forResource: "hoge", ofType: "txt") {
  do {
// ファイルの内容を取得する
    let content = try String(contentsOfFile: path)
// print("content: \(content)")
    let xml = try XML.parse(content)
    var i = 0
    while true {
      guard let str = xml["root", "data", i, "value"].text else { break; }
      print("\(str)")
      i += 1
    }
    print("ループ終わり")
  } catch  {
    print("ファイルの内容取得時に失敗")
  } // do
} else {
  print("指定されたファイルが見つかりません")
} // if let

真面目にやると結構大変です。XMLParser()を使うのだけれど、 要素(エレメント、属性)が1つ見つかる度に、XMLParserDelegateにあるparse(..)という名前の 関数が呼び出されて、結果を取り出して自分で使えるように加工しないといけません。 この辺りを全部やってくれるのが、上記のyahoojapanのコードです。

if let 変数 と guard let 変数

if let a = myFunc() {成功したらやること} else {失敗したらやること}

なお、myFunc()は、成功時は値を返して、失敗時はnil(NULLのこと)を返すように実装する。 これで、通常動作と失敗時の動作が両方書ける。なお、失敗時はaにはnilが入る。

if guard let a = myFunc() else {中断; returnとかexit(-1)とか}

成功時は処理を続けるが、失敗したら処理が続けることができない時の実装形式。 myFunc()は、成功時は値を返して、失敗時はnil(NULLのこと)を返すように実装する。 aには値しか入らない。nilが入ることはなく、失敗時は次の処理ができない。

失敗と成功の処理をしっかり書きなさいというルールです。 なお、if let a = の方は、aがnilかもしれないので、aの値を普通に使おうとすると、 nilを考えてないぜ! っというビルドエラーになる。

SwiftUIでコード内の文字をWebKitで表示してjavascriptの結果を受け取る

ContentView.swift

import SwiftUI
 
struct ContentView: View {
    var body: some View {
        WebView(url: URL(string: "dummy")!)
    }
}

WebView.swift

import SwiftUI
import WebKit
 
struct WebView: UIViewRepresentable {
    var url: URL
    
    func makeUIView(context: Context) -> WKWebView {
        let webConfig = WKWebViewConfiguration()
        let userController = WKUserContentController()
        userController.add(makeCoordinator(), name: "hoge")
        webConfig.userContentController = userController
        let wkWebView = WKWebView(frame: .zero, configuration: webConfig)
        return wkWebView
    }
    
    func updateUIView(_ uiView: WKWebView, context: Context) {
        // 普通にurlでブラウズするコード
        // let req = URLRequest(url: url)
        // uiView.load(req)
 
        // ローカルファイルindex.htmlを表示するコード
        // guard let path: String = Bundle.main.path(forResource: "index", ofType: "html")
        //     else { return }
        // let localHTMLUrl = URL(fileURLWithPath: path, isDirectory: false)
        // uiView.loadFileURL(localHTMLUrl, allowingReadAccessTo: localHTMLUrl)
 
        let str = "<!DOCTYPE html>"
        + "<html>"
        + "<head>"
        + "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/>"
        + "<title>スマホでの見え方調整</title>"
        + "<meta name=\"viewport\" content=\"width=280\" />"
        + "</head>"
        + "<body>"
        +   "<input type=\"button\" value=\"Exec\" onclick=\"var number = 123;"
        +   "webkit.messageHandlers.hoge.postMessage(number);\" /><br />"
        +   "<br />"
        + "</body>"
        + "</html>"
        uiView.loadHTMLString(str, baseURL: nil)
    }
 
    func makeCoordinator() -> WebView.Coordinator {
        return Coordinator()
    }
    
} // struct
 
extension WebView {
    class Coordinator: NSObject, WKNavigationDelegate, WKScriptMessageHandler {
       func userContentController(
           _ userContentController: WKUserContentController,
           didReceive message: WKScriptMessage) {
               if message.name == "hoge" {
                 let number = message.body as! Int
                 print("JavaScript is sending a number \(number)")
            }
        }
    }
}
 
struct WebView_Previews: PreviewProvider {
    static var previews: some View {
        WebView(url: URL(string: "dummy")!)
    }
}