SwiftUIでローカルのindex.htmlをWebKitで読んでjavascriptの結果をSwiftコードで読む

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) {
        // let req = URLRequest(url: url)
        // uiView.load(req)
        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)
    }
/*
     これだと動かない
     func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
        if message.name == "hoge" {
            print("JavaScript is sending a message \(message.body)")
        }
    }
*/
    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")!)
    }
}

index.htmlを新規作成(WebView.swiftと同じフォルダーに置く)

<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
  <title></title>
  <meta charset="utf-8" />
</head>
<body>
  <input type="button" value="Exec" onclick="var number = 123; webkit.messageHandlers.hoge.postMessage(number);" /><br />
  <br />
  <div id="output"></div>
</body>
</html>

実行結果(デバッグコンソールに) JavaScript is sending a number 123
なお、ブラウザに出るボタンがめちゃちっちゃい!

画面に図形を表示するには GeometryReaderとPath

(4) 画面に図形を表示するには
(4.1) 四角を表示、大きさはおまかせ
struct ContentView : View {
var body: some View {
VStack {
  Text("Hello There!")
  Rectangle().fill(Color.blue)
}.frame(width: 150, height: 100).border(Color.black)
}
}
(4.2) 四角を表示、元の表示場所から右に+5外れて大きさを半分に
struct ContentView : View {
var body: some View {
VStack {
  Text("Hello There!")
  GeometryReader { geometry in
    Rectangle()
    .path(in: CGRect(
       x: geometry.size.width + 5,
       y: 0,
       width: geometry.size.width / 2.0,
       height: geometry.size.height / 2.0))
    .fill(Color.blue)
  }
}.frame(width: 150, height: 100).border(Color.black)
}
}
 
(4.3) 関数にした
struct ContentView : View {
var body: some View {
VStack {
  Text("Hello There!")
  MyRectangle()
}.frame(width: 150, height: 100).border(Color.black)
}
}
 
struct MyRectangle: View {
var body: some View {
GeometryReader { geometry in
  Rectangle()
  .path(in: CGRect(
    x: geometry.size.width + 5,
    y: 0,
    width: geometry.size.width / 2.0,
    height: geometry.size.height / 2.0))
  .fill(Color.blue)
  }
}
}
 
(4.4) 自由な形を表示
struct ContentView : View {
var body: some View {
HStack {
  Text("SwiftUI")
  .foregroundColor(.black).font(.title).padding(15)
  .background(RoundedCorners(color: .green, tr: 30, bl: 30))
  Text("Lab")
  .foregroundColor(.black).font(.title).padding(15)
  .background(RoundedCorners(color: .blue, tl: 30, br: 30))
} // HStack
.padding(20).border(Color.gray).shadow(radius: 3)
} // some View
} // View
 
struct RoundedCorners: View {
var color: Color = .black
var tl: CGFloat = 0.0
var tr: CGFloat = 0.0
var bl: CGFloat = 0.0
var br: CGFloat = 0.0
var body: some View {
GeometryReader { geometry in
Path { path in
  let w = geometry.size.width
  let h = geometry.size.height
  // We make sure the redius does not exceed the bounds dimensions
  let tr = min(min(self.tr, h/2), w/2)
  let tl = min(min(self.tl, h/2), w/2)
  let bl = min(min(self.bl, h/2), w/2)
  let br = min(min(self.br, h/2), w/2)
  path.move(to: CGPoint(x: w / 2.0, y: 0))
  path.addLine(to: CGPoint(x: w - tr, y: 0))
  path.addArc(
    center: CGPoint(x: w - tr, y: tr),
    radius: tr,
    startAngle: Angle(degrees: -90),
    endAngle: Angle(degrees: 0),
    clockwise: false)
  path.addLine(to: CGPoint(x: w, y: h - br))
  path.addArc(
    center: CGPoint(x: w - br, y: h - br),
    radius: br,
    startAngle: Angle(degrees: 0),
    endAngle: Angle(degrees: 90),
    clockwise: false)
  path.addLine(to: CGPoint(x: bl, y: h))
  path.addArc(
    center: CGPoint(x: bl, y: h - bl),
    radius: bl,
    startAngle: Angle(degrees: 90),
    endAngle: Angle(degrees: 180),
    clockwise: false)
  path.addLine(to: CGPoint(x: 0, y: tl))
  path.addArc(
    center: CGPoint(x: tl, y: tl),
    radius: tl,
    startAngle: Angle(degrees: 180),
    endAngle: Angle(degrees: 270),
    clockwise: false)
} // Path
.fill(self.color)
} // GeometryReader
} // some View
} // View

SwiftUIでURLを入力してボタンを押したらWEBページを表示する

ContentView.swift

import SwiftUI
 
struct ContentView: View {
@State var workUrl: String = ""
@State var fixedUrl: String = "https://www.google.com/"
var body: some View {
VStack(alignment: .leading) {
    TextField(" Enter Url (例 https://apple.com)", text: $workUrl)
    Text("\(fixedUrl)")
    Button(action: {
        self.fixedUrl = self.workUrl
    }) {
        Text("go url")
    }
    WebView(url: URL(string: fixedUrl)!)
}.padding() // VStack
} // some View
} // View
 
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

WebView.swift ContestView.swiftを変更する

import SwiftUI
import WebKit
 
struct WebView: UIViewRepresentable {
    var url: URL
    func makeUIView(context: Context) -> WKWebView {
        return WKWebView(frame: .zero)
    }
    func updateUIView(_ uiView: WKWebView, context: Context) {
        let req = URLRequest(url: url)
        uiView.load(req)
    }
}
 
struct WebView_Previews: PreviewProvider {
    static var previews: some View {
        WebView(url: URL(string: "https://apple.com")!)
    }
}

SwiftUIでWEBブラウザを作るには

WebView.swiftを新しく作る

import SwiftUI
import WebKit
 
struct WebView: UIViewRepresentable {
    var url: URL
    func makeUIView(context: Context) -> WKWebView {
        return WKWebView(frame: .zero)
    }
    func updateUIView(_ uiView: WKWebView, context: Context) {
        let req = URLRequest(url: url)
        uiView.load(req)
    }
}
 
struct WebView_Previews: PreviewProvider {
    static var previews: some View {
        WebView(url: URL(string: "https://apple.com")!)
    }
}

ContestView.swiftを変更する

import SwiftUI
 
struct ContentView: View {
    var body: some View {
      WebView(url: URL(string: "https://apple.com")!)
    }
}
 
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

SwiftUI プレビュー画面を作るには

(3) プレビュー画面を作るには
(3.1) ContentView()の例
struct ContentView: View {
// こっちは表示用
}
// これでプレビューができる
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
  ContentView()
}
}
(3.2) LandmarkList()の例
struct LandmarkList: View {
// こっちは表示用
}
 
// これでプレビューができる
struct LandmarksList_Previews: PreviewProvider {
static var previews: some View {
  LandmarkList().environmentObject(UserData())
}
}
 
// プレビューを2つ表示
struct LandmarksList_Previews: PreviewProvider {
static var previews: some View {
  ForEach(["iPhone SE", "iPhone XS Max"], id: \.self) { deviceName in
    LandmarkList()
    .previewDevice(PreviewDevice(rawValue: deviceName))
    .previewDisplayName(deviceName)
  }.environmentObject(UserData())
}
}
(3.3) LandmarkDetail()の例
struct LandmarkDetail: View {
// こっちは表示用
}
 
// これでプレビューができる。表示対象は0番目 landmarks[0]
struct LandmarkDetail_Preview: PreviewProvider {
static var previews: some View {
  let userData = UserData()
  return LandmarkDetail(landmark: userData.landmarks[0])
  .environmentObject(userData)
}
}
 
(3.4) LandmarkRow()の例
struct LandmarkRow: View {
// こっちは表示用
}
 
// これでプレビューができる。表示対象は0,1番目
struct LandmarkRow_Previews: PreviewProvider {
static var previews: some View {
  Group {
    LandmarkRow(landmark: landmarkData[0])
    LandmarkRow(landmark: landmarkData[1])
  }
  .previewLayout(.fixed(width: 300, height: 70))
}
}

SwiftUIで画面に色々表示するには

(2) 画面に色々表示するには
(2.1) ContentView()の例
struct ContentView: View {
var body: some View {
「ここ」に書く
}
}
 
(2.1.1) 表示の中身
struct ContentView: View {
var body: some View {
VStack { // 縦に並べる
  MapView() // 地図
  CircleImage() // 丸いイメージ画像
  VStack {
    Text("文字") // 文字
    HStack { // 横に並べる
      Text("文字") // 文字
      Spacer() // 空白
      Text("文字") // 文字
    }
  }.padding() // 周囲に空白
  Spacer() // 空白
} // VStack
} // some View
} // View
 
(2.2) LandmarkList()の例
struct LandmarkList: View {
@EnvironmentObject private var userData: UserData // UserData()が変更されたら画面が変わる
var body: some View {
「ここ」に書く
}
}
 
(2.2.1) 表示の中身
struct LandmarkList: View {
@EnvironmentObject private var userData: UserData
var body: some View {
NavigationView {
List {
  Toggle(..) {..} // オン・オフできるボタン
  ForEach(並べるもの達) { 1個の表示物 in
    if それが表示対象なら {
      NavigationLink(タップしたら開く画面) {
        表示するもの※
      }
    }
  }
}.navigationBarTitle(表題)
} // NavigationView
} // some View
} // View
 
(2.2.2) タップしたら開く画面
struct LandmarkDetail: View {
@EnvironmentObject var userData: UserData // UserData()が変更されたら画面が変わる
var landmark: Landmark // これで表示対象を受け取る
var body: some View {
VStack { // 縦に並べる
  MapView() // 地図
  CircleImage() // 丸いイメージ画像
  VStack { // 縦に並べる
    HStack { // 横に並べる
      Text("文字") // 文字
      Button(action: {タップしたらすること}) { // ボタン
        ボタンの表示内容★とか☆
      } // Button
    } // HStack
  }.padding() // 周囲に空白
  Spacer() // 空白
} // VStack
} // some View
} // View
 
(2.2.3) 表示するもの※
struct LandmarkRow: View {
var landmark: Landmark // これで表示対象を受け取る
var body: some View {
HStack { // 横に並べる
  landmark.image // 画像
  Text(landmark.name) // 文字
  Spacer() // 空白
  if landmark.isFavorite { // お気に入りなら
    Image() // 星の画像
  }
} // HStack
} // some View
} // View

SwiftUIで最初に開く画面を決めるには

(1) 最初に開く画面を決める
SceneDelegate.swiftの
window.rootViewController = UIHostingController(rootView: 「ここ」)
に書く。
 
// ContentView()を設定する場合
window.rootViewController = UIHostingController(rootView: ContentView())
 
// LandmarkList()を設定する場合
window.rootViewController = UIHostingController(rootView: LandmarkList())
 
// UserData()が変更されたら画面が変わるようにして、LandmarkList()を設定
window.rootViewController = UIHostingController(rootView: LandmarkList().environmentObject(UserData()))