SwiftUI Webサーバーから文字列取得する getHTTP

SwiftUIでボタンを押すとWebサーバーから文字列を取得する方法。

ObservableObjectを継承したクラスを作成する。

class TNetHTTPClient:ObservableObject {
    @Published var responseString: String
    init() {
        self.responseString = ""
    }
    func get(aurl: String) -> Void {
        guard let url1 = URL(string: aurl) else { return }
        URLSession.shared.dataTask(with: url1) { (data, res, _) in
            guard let data = data else { return }
            DispatchQueue.main.async {
                print(res ?? "")
                if let res = res as? HTTPURLResponse {
                    if res.statusCode == 200 {
                        self.responseString = " \( String(data: data, encoding: .utf8) ?? "" ) "
                    } else {
                        self.responseString = "status code = \(res.statusCode)"
                    }
                }
            }
        }.resume()
    }
}

Viewの構造体を作成しWebサーバーに接続するためのButtonと、HTTPレスポンスした文字列表示するためのTextFieldを配置します。

struct GetHttp: View {
    let http_url: String = "https://mjeld.com/moji.txt"
    @ObservedObject var netHTTPClient1 = TNetHTTPClient()
    var body: some View {
        VStack{
            Text("Get a string from the server.")
            TextField(self.http_url, text: self.$netHTTPClient1.responseString)
                .padding(.horizontal)
            HStack{
                Button("Button1", action: {
                    self.netHTTPClient1.get(aurl: self.http_url)
                })
                Button("Button2", action: {
                    self.netHTTPClient1.responseString = ""
                })
            }
            Spacer()
        }
    }
}

@ObservedObjectで先に作っていたクラスTNetHTTPClientを宣言します。TextFieldのtextは$netHTTPClient1.responseStringを設定しresponseStringが変更した場合TextFieldに反映されます。Button1をタップするとnetHTTPClient1.get(aurl: self.http_url)が実行されます。

URLからデータを取得しTextFieldに表示する
URLからデータを取得しTextFieldに表示する

実行し、Buttonを押すと上のようにWebサーバーの文字列を取得します。Webサーバー側にはUTF8のプレーンテキストファイルを置いています。

https://developer.apple.com/documentation/foundation/urlsession/1411554-datatask

SwiftUI Pull To Refresh用の部品

SwiftUIで簡単にPull To Refreshできる部品をGitHubに公開してくれていて、下記URLからダウンロードすることも可能です。

https://github.com/siteline/SwiftUIRefresh

Cocoapodsから取得する場合Podfileファイルに下記を追加します。

pod "SwiftUIRefresh"

pod install updateしimport SwiftUIRefreshすると下記の候補が出てくるようになります。

.pullToRefresh(isShowing: Binding<Bool>, onRefresh: () -> Void)

Usageをまねて下記のコードを書きました。

import SwiftUI
import SwiftUIRefresh

struct NavigationRefresh: View {
    @State var isShowing: Bool = false
    var body: some View {
        NavigationView {
            List (["Ceres", "Toutatis", "Phaethon"], id: \.self) {s in
                Text(s)
            }.navigationBarTitle(Text("SwiftUIRefresh"))
        }.pullToRefresh(isShowing: self.$isShowing){
            DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
                self.isShowing = false
            }
        }
    }
}

実行すると下のような挙動です。

Pull To Refresh
Pull To Refresh

SwiftUI NavigationView NavigationLink View切替

NavigationView内に NavigationLinkを実装しdestination内にViewを実装することが可能です。実行後、NavigationLinkをタップするとdestinationへ遷移します。この場合、NavigationViewがないとNavigationLinkは正常な動作をしないようです。

struct Navi1: View{
    @State var active1: Bool = false
    var body: some View{
        NavigationView{
            NavigationLink(destination: Text("Destination1"), isActive: self.$active1){
                Text("NavigationLink1")
            }
        }
    }
}

上記のコードはすごくシンプルな例です。NavigationLink1をタップするとText(“Destination1”)が表示されます。

このNavigationLinkと同じ動作をButtonにさせたい場合は下記のようなコードを書きます。

struct NavigationPane: View {
    let messageDefault = "Destination tag=101"
    @State var iClick: Int?
    @State var sDestination: String = ""
    var body: some View {
        NavigationView{
            VStack{
                NavigationLink(destination:
                    VStack{
                        Text(self.sDestination).onDisappear(perform: {
                            self.sDestination = self.messageDefault
                        })
                        Spacer()
                    }, tag: 101, selection: self.$iClick){
                    Text("NavigationLink1")
                }
                .padding(.bottom)
                NavigationLink(destination:
                    VStack{
                        Text("NavigationLink tag=102")
                        Spacer()
                }, tag: 102, selection: self.$iClick){
                    Text("NavigationLink2")
                }.padding(.bottom)
                Button("Button1",action: {
                    self.sDestination = "Push the Button1 to the destination tag=101"
                    self.iClick = 101
                })
                Spacer()
            }.onAppear(perform: {
                self.sDestination = self.messageDefault
            })
        }
    }
}

上記コードが実行された場合は下のようなイメージです。

NavigationLink1NavigationLink2Button1が表示されています。NavigationLinkは通常通りタップするとdestinationが表示されます。

Buttonをタップするとtag: 101側のNavigationLink destinationが表示されます。その場合、Textには「“Push the Button1 to the destination tag=101”」と表示させています。

NavigationLinkのselection値の変数を変更することで他のNavigationLink destinationが反応してくれるようです。