SwiftUI スクリーンサイズ取得 UIScreen

UIScreenサイズ取得

SwiftUIで、UIScreenを使うとスクリーンサイズが取得できます。UIScreen.mainがinternal screenです。 UIScreen.main.boundsはCGRectでsize: CGSizeが入っています。このsizeからスクリーン縦横幅が取得できます。

struct TScreen: View {
    @State var screen: CGSize!
    var body: some View {
        VStack {
            Text("iPhone 画面サイズ取得").font(.title)
            if screen != nil {
                Text("width = \(screen.width)")
                Text("height = \(screen.height)")
            }
            Spacer()
        }.onAppear(){
            screen = UIScreen.main.bounds.size
        }
    }
}

SwiftUI ListとSection・データ更新について

SwiftUI-ListData

SwiftUIListは、データなどをリスト表示させる場合便利なコントロールです。このListSectionを持たせることができ ヘッダー・フッターをつけることもできます。LIstのシンプルな記述方法は下記です。

struct TListBox: View {
    var body: some View {
        List{
            Text("アイテム1")
            Text("アイテム2")
        }
    }
}
シンプルなList
シンプルなList

このコードの場合Textを個別に入れています。この記述方法ではList内のアイテムが多い場合対応できません。例えば100個のリストを作成する場合は下のように記述できます。

struct TListBox: View {
    var body: some View {
        List(0..<100){
            Text("アイテム\($0 + 1)")
        }
    }
}
100個のアイテムを作成
100個のアイテムを作成

Listのカッコ内に、Range式を入れることができます。Rangeは下記のように記述もできます。

var range: Range = 0..<10

RangeisEmpty, clamped, overlapsなどの機能も持っています。List内でForEachが記述できるのでRangeを使うことが可能です。

struct TListBox: View {
    var forEachText = ForEach(0..<10){
        Text("\($0 + 1)")
    }
    var body: some View {
        List{
            self.forEachText
        }
    }
}

ForEachforEachText変数に入れていますが、Listの中に直接書くこともできます。

Listに配列などが入れば便利なのですが、「List([1,2])」このようには記述できないようです。

Initializer 'init(_:rowContent:)' requires that 'Int' conform to 'Identifiable'

配列の場合、下のコードのように id指定が必要です。

List([1,2,3], id: \.self){
	Text("\($0)")
}

下のようなIdentifiable が付いた構造体の場合、配列をそのままList()に設定することが可能です。

struct TListItem: Identifiable {
    var id: Int
    init(_ i: Int) {
        id = i
    }
}

struct TListBox: View {
    var items: Array<TListItem> = [TListItem(1),TListItem(2)]
    var body: some View {
        List(items){
            Text("アイテム\($0.id)")
        }
    }
}

上のコードは、Identifiableを付けたTListItemの配列をList()内で使っています。

List内にはSectionを使いヘッダーとフッターをつけることもできます。

struct TListBox: View {
    var forEachText = ForEach(0..<2){
        Text("\($0 + 1)")
    }
    var body: some View {
        List{
            ForEach(0..<2){
                Section(header: Text("ヘッダー\($0 + 1)"), footer: Text("フッター\($0 + 1)")){
                    self.forEachText
                }
            }
        }
    }
}

List()のカッコ内に構造体配列の変数を設定することで動的にListを更新できます。下のコードは、Timerを使ってデータ内容を更新させListに反映させる例です。

struct TListItem: Identifiable {
    var id: Int
    var t: String
    init(_ i: Int, _ s: String) {
        id = i
        t = s
    }
}

struct TListBox: View {
    @State var listItems: [TListItem] = [];
    @State var count: Int = 0
    @State var timer1: Timer?
    var body: some View {
        List(listItems){
            Text($0.t)
        }.onAppear(){
            self.timer1?.invalidate()
            self.timer1 = Timer.scheduledTimer(withTimeInterval: 0.8, repeats: true) {_ in
                if (self.count <= 100) {
                    let df = DateFormatter()
                    df.dateFormat = "🕐 mm分ss秒SSS"
                    self.listItems.append(TListItem(self.count, df.string(from: Date())))
                    self.count += 1
                }
            }
        }
    }
}
タイマーを使ってList内データ更新
タイマーを使ってList内データ更新

上のように、List内アイテム間に線が入ってしまいます。これを回避するにはonAppear時に「separatorStyle = .none 」を設定します。

List(listItems){
	Text($0.t)
}.onAppear { UITableView.appearance().separatorStyle = .none }
.onDisappear { UITableView.appearance().separatorStyle = .singleLine }
Listアイテム間の線を消す
Listアイテム間の線を消す

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表示される