Swift JSON文字列からenumへDecodeについて

Swift言語で、下記はシンプルなenumの書き方です。

enum MobileArmor {
    case bigro, zakrello
}

MobileArmorというenum(列挙型)を作成しました。このMobileArmorを変数に入れたい場合下記のように記述します。

let mobileArmor: MobileArmor = MobileArmor.zakrello

mobileArmor変数の中を.zakrelloに設定しています。この変数の状態によってswitch文で処理を変更したい場合下記のように書きます。

let mobileArmor: MobileArmor = .zakrello
switch mobileArmor {
case .zakrello:
    print("zakrello")
    break
case .bigro:
    print("bigro")
}

enumCaseIterableHashable, Codableなどをprotocolをつけることが可能です。

CaseIterableの場合AllCasesが使えるようになります。このAllCasesを使えば下記のコードのようにenumに設定している内容を確認することができます。

enum MobileArmor: CaseIterable {
    case bigro, zakrello
}
MobileArmor.allCases.forEach { print ( $0.self) }
型の内容をallCases.forEachで取得

enum型にHashableを設定した場合は、hashValuehash(into hasher: inout Hasher)が使えますがHashableを設定しなくてもhashValueなどは利用できるようでした。hash(into: &hasher)を設定するためのHasherは、任意のシーケンスを整数にマッピングするために使用できます。combine()とfinalize()をつかうようです。下記に例を書いてみますがあってるのか不明です。

enum MobileArmor: CaseIterable, Hashable {
    case bigro, zakrello
}
let mobileArmor: MobileArmor = .zakrello
var hasher1 : Hasher = Hasher()
hasher1.combine(128)
hasher1.finalize()
mobileArmor.hash(into: &hasher1)

Codableも他と同じく enumに設定できるprotocolです。Codableは下のコードのようにDecodableEncodableが合わさった機能を持っています。

public typealias Codable = Decodable  & Encodable

MobileArmor enum型にStringCodableを設定しました。Codableを指定しているので JSONのDecodeが可能です。

enum MobileArmor: String, Codable, CaseIterable, Hashable {
    case bigro = "ビグロ"
    case zakrello = "ザクレロ"
}

do {
    let amobileArmor = try JSONDecoder().decode(MobileArmor.self, from: "\"ザクレロ\"".data(using: .utf8)! )
    switch amobileArmor {
    case .zakrello:
        print("zakrello")
        break
    case .bigro:
        print("bigro")
    }
} catch { }

ザクレロ」という文字列をMobileArmor型にdecodeできました。JSONDecoderを使わない方法もあると思いますが、手っ取り早いコード例です。Codableが設定されているので、JSONEncoder()を使えば下のコードのように文字列に変換することもできます。

do {
    print( String(data: try JSONEncoder().encode(MobileArmor.bigro), encoding: .utf8)! )
} catch { }

Swift 文字列置換え replacingOccurrences

Swift replacingOccurrences

Swift言語で、C#のString.Replace()などの文字列置換えしたい場合 replacingOccurrences()が利用できます。replacingOccurrencesは、指定した文字部分すべてが別の指定された文字列で置き換えられ新しい文字列を返す関数です。下のコードは、Swift 5.2で試した文字列置換え例です。この関数は、置換えたい文字 ofと置換えたあとの文字withを引数として渡します。returnされる文字列はwithで指定した文字に置換わっています。

let s = String("ウルトラマン").replacingOccurrences(of: "マン", with: "セブン")
print (s)

上記コードは「ウルトラマン」の「マン」を「セブン」に置換えます。変数 sに入るのは「ウルトラセブン」です。

下記のURLではrangereplaceSubrangeを使った例です。

https://ameblo.jp/mojeld/entry-12593491674.html

SwiftUI Webサーバーの画像をImageに表示させる

restimage1

SwiftUIImageImage(“chincoteague”)と書けばプロジェクト内「Assets.xcassets」に配置した“chincoteague”画像を利用することができます。

しかし、Assetsに配置した場合インターネットなどから 動的にアプリ内画像変更などができません。そういった場合に対処するため、Webサーバーに配置された画像をアプリ内のImageで表示させる方法です。

Imageにはinit(uiImage: UIImage)がありますが、ここにWebサーバーから取得した画像データを埋めれば実現できます。Webサーバーのデータ取得方法は前回SwiftUIからWebサーバー文字列を取得する方法を書いていますが、この方法と同じくURLSessionを使います。

URLSession.shared.dataTask(with: URL(string: "https://mojeld.com/img/charleyrivers.jpg")!) { d1, _, _ in
	
}

dataTask()にURLを入れると、{ d1, _, _ in }の中のd1にデータが入ります。取得後UIImage()にd1を設定すれば画像データがUIImage()に入ります。

let d2 = UIImage(data: d1)

Webサーバーからの画像取得からUIImageに設定するまでの動作をObservableObject継承したクラスにまとめます。

class TURLImage: ObservableObject {
    enum TNetStatus { case none; case standby; case successfll }
    @Published var netStatus: TNetStatus = .none
    @Published var img : UIImage = UIImage()
    func getImg(_ urlStr: String) -> Void {
        guard let url1 = URL(string: urlStr) else { return }
        netStatus = .standby
        URLSession.shared.dataTask(with: url1) { data, _, _ in
            guard let d1 = data,
                let d2 = UIImage(data: d1, scale: 5) else {
                return
            }
            DispatchQueue.main.asyncAfter(deadline: .now() + 1 ) {
                self.netStatus = .successfll
                self.img = d2
            }
        }.resume()            
    }
}

TURLImageという名称のクラスを作成しました。そこにはTNetStatusというステータスを示すenumUIImageを@Publishedに置いています。@Publishedに配置した場合View側でクラス内の変更を知ることができます。配置したUIImageのscaleを「5」に設定しています。これたぶんscale 1/5ということだと思います。クラス内のvar img : UIImageの更新は、DispatchQueue.main.asyncAfter()を使って1秒間待ってから処理しています。これは読込み中描画を1秒以上表示したかったからです。

つぎは、View側の処理です。iOSアプリ画面のボタンを押すと「読み込み中…」を表示しWebサーバーからデータを取得するまでのコードが下記です。

struct TNetImage: View {
    @State var uStr: String
    @ObservedObject var urlImage: TURLImage = TURLImage()
    var body: some View {
        VStack {
            if ( self.urlImage.netStatus == TURLImage.TNetStatus.successfll ) {
                Image(uiImage: self.urlImage.img)
            } else if ( self.urlImage.netStatus == TURLImage.TNetStatus.standby) {
                Text("読み込み中…")
            }
            Button("Button1", action: {
                self.urlImage.getImg(self.uStr)
            } )
            Spacer()
        }
    }
}

TNetImageというViewを作成し、 「Button1」を押したあと「読み込み中…」が表示されWebサーバーからの画像データ取得完了後Image表示に切り替わります。

ボタンを押すと「読み込み中が表示され」読み込み完了後Image表示される
ボタンを押すと「読み込み中が表示され」読み込み完了後Image表示される