タブレット用プログラムの書き止め

android OS & iPadOS の記録。

【Swift P4.5.1】SideBar

2024-11-05 19:49:07 | Swift iPadOS用

サイドバーと広域変数の組み合わせの基本動作を確認する。

サイドバーにはListを利用するのだが今回はボタンで手抜き。
普通はパスをやり取りするが今後の画像編集を考えて画像データを利用している。


【MyApp.swift】

@main
struct MyApp: App {
    @StateObject var dataModel = DataModel()

    var body: some Scene {
        WindowGroup {
            ContentView()
                .environmentObject(dataModel)
        }
    }
}

【ContentView.swift】

struct ContentView: View {
    @EnvironmentObject var dataModel: DataModel
    @State private var visibility: NavigationSplitViewVisibility = .all  // 分割メニュー設定
    
    var body: some View {
        NavigationSplitView(columnVisibility: $visibility) {
            VStack{
                Button("Test") {
                    dataModel.imageManager.originalImage = UIImage(named: "Test")
                }
                Button("Test2") {
                    dataModel.imageManager.originalImage = UIImage(named: "Test2")
                }
            } 
     // } content: { これを追加すると3列になる
        } detail: {
            ImageView()
                .environmentObject(dataModel)
        }
    }
}


【ImageView.swift】

struct ImageView: View {
    @EnvironmentObject var dataModel: DataModel
    
    var body: some View {
        if let image = dataModel.imageManager.originalImage {
            Image(uiImage: image)
                .resizable()
                .scaledToFit()
        }
        else {
            Image(systemName: "doc")
            Text("No data")
        }
    }
}


【DataModel.swift】広域変数

class DataModel: ObservableObject {
    @Published var imageManager: ImageManager = ImageManager()
}


【ImageManager.swift】機能別に管理する予定

struct ImageManager {
    var originalImage: UIImage? = nil
}

 


  • X
  • Facebookでシェアする
  • はてなブックマークに追加する
  • LINEでシェアする

【Swift P4.5.1】広域変数について

2024-10-25 16:16:06 | Swift iPadOS用

@StateObject
ObservableObject
@EnvironmentObject

これらを利用することでほぼ全てでアクセス可能な変数が作れる。
ただ、変数が更新されると関連付けられているViewも自動更新されるのでルールが有り制限も受ける。
勘違いによるデータの書き換え等の抑制のためにルールが存在する。

制限を受けたくない場合はグローバル変数を使う。
個人が全てを管理してチェックを厳しく行わないとバグが発生する。


共有変数を body{ } 内で利用しないと、エラーもしくはクラッシュを引き起こす。
共有変数は関連項目に自動更新を施すためにその枠外の変数に写し利用されると問題が起こりやすくなる。


body { } 内で共有変数の値を利用して、手順をもって共有変数に代入する事。
bodyプロパティに @ViewBuilderアトリビュートが付いているそうで 宣言式以外ではViewが戻らないとエラーとなる。

let image = 共有変数 // OK
共有変数 = 値    // NG

Button { // コード部分
 共有変数 = 値 // OK
} label: { }

UI のコード部分での代入はUIがViewを戻すので問題ないみたい。

@StateObjectにするか、グローバル変数にするか練習が終わってからかな。

 

【ChildView.swift】

struct ChildView: View {
    @EnvironmentObject var dataModel : DataModel

    /******** 【NG】 エラーは出ないがクラッシュする

    @State private var image: UIImage?
    @State private var imageSize: CGSize = CGSize.zero
    init() {
        image = dataModel.secoundImage
        imageSize =  image!.size
    }
    var body: some View {
        Image(uiImage: image!)
            .resizable()
            .scaledToFit()
            .frame(width: imageSize.width )
    }

    **********/


    @State private var isChange: Bool = true
    var body: some View {
        let image = dataModel.secoundImage
        if  image != nil {
            Image(uiImage: image!)
                .resizable()
                .scaledToFit()
                .frame( width: image!.size.width )

        } else {
            Text("no data")
            // 共有変数に代入の式ではViewを返さないのでエラー。UIの実行部分に記述すればOK。
            // dataModel.secoundImage = UIImage(named: "Test2") 代入には場所を選ぶ。if文内はダメ。
        }
        Button { // ButtonはViewを返しているのでエラーにならない
            dataModel.secoundImage = UIImage(named: isChange ? "Test2": "Test")
            isChange.toggle()
        } label: {
            Text("Change")
        }
    }
}

【ContentView.swift】

struct ContentView: View {
   @EnvironmentObject var dataModel: DataModel

    var body: some View {
        VStack {
            if let image = dataModel.originalImage
            {
                Image(uiImage: image)
                    .resizable()
                    .scaledToFit()
            }
        }
        .navigationBarTitleDisplayMode(.inline)
        .toolbar {
            ToolbarItem(placement: .navigationBarLeading) {
                Text("Child ").bold()
            }
            ToolbarItem(placement: .navigationBarLeading) {
                NavigationLink {
                    ChildView()
                        .environmentObject(dataModel) // 必須
                } label: {
                    Image(systemName: "crop")
                }
            }
        }
    }
}

【Main.swift】

@main
struct MyApp: App {
    @StateObject var dataModel = DataModel()
    var body: some Scene {
        WindowGroup {
            NavigationStack {
                ContentView()
                    .environmentObject(dataModel) // 必須
            }
            .navigationViewStyle(.stack)
        }
    }
}

【DataModel.swift】

class DataModel: ObservableObject {
    @Published var originalImage: UIImage? = nil
    @Published var secoundImage: UIImage? = nil

    init() {
        originalImage = UIImage(named: "Test3")
        secoundImage = UIImage(named: "Test")
    }
}

 


  • X
  • Facebookでシェアする
  • はてなブックマークに追加する
  • LINEでシェアする

【Swift P4.5.1】Retinaディスプレイについて

2024-10-18 23:03:43 | Swift iPadOS用

単純に画像を表示するのは綺麗。
ピンチで拡大したら、綺麗。

アプリユーザーは座標を細かく意識しなくていいので綺麗な画面はとても良い。

 

ただ、画像を編集しようとプログラミングする時に…。え?どういう事!

.Fit や .Fillはスクリーンショットに近いイメージなんだけど、
画像ファイルを読み込みそのまま表示させた時に違和感が出る。

Retinaの構造上?の事から起こるものらしい。

 

作業にてiPadでスクリーンショットを撮った画像ファイルをUIImageで読み込みスケール1でそのまま表示する。
画像widthはデバイス横幅と同じ? 画像ファイルは 2,360x1,640サイズ。(iPadのRetina公称同じ)
なのに表示されている画像はデバイス画面の中に入りきらず画像面積1/4(左上)部分になっている。

そして、自作スクロールと画像オフセットで画像自体の座標を取得すると...。
画像右下スクロール座標取得で画像MaxWidth、MaxHeightで画像座標はほぼ正常だった。

 


Swift iPad用プログラミングを初めてする者には戸惑う事柄。

ピクセルには物理ピクセルと論理ピクセルがある。
物理ピクセルは、1ドット=1ピクセル
論理ピクセルは、1ドット=複数ピクセル(2x2等)

スクリーンショットして画像ファイルに保存すると、2,360x1,640サイズになる。
プログラミングで画像ファイルから読み込みプレーン表示すると、1,180x820ドット部分しかデバイスに表示されない。

 

日記を書くにあたり、フルスクリーンの時のViewサイズを調べてみた。(今更だけど)
親viewの幅は 1,180、高さ 820 …。ふ~む^^;

今まで確認してなかった(笑)
確かに論理ピクセル(2x2)でした。

 

画像とデバイスの座標相関を留意しつつプログラミングする事が大切だと分かった。
C++脳には理解に時間がかかる(´;ω;`)

 

 

 


  • X
  • Facebookでシェアする
  • はてなブックマークに追加する
  • LINEでシェアする

【Swift P4.5.1】Dragに二つの処理を割り当てる

2024-10-15 22:35:09 | Swift iPadOS用

ダブルタップで、isModeを true <-> false と切り替える。(ボタンで切り替えるのも良いかな)
true の時にアイコンを表示して画像をドラッグでスクロール。
falseの時にアイコンを非表示にしてドラッグ座標値を処理する。(切り抜きとか)

struct SubView: View {
    
    // 省略
    @State private var handLocation = CGPoint.zero

    func drag_scroll(size: CGSize) {
        // 変更無しなので省略
    }
    
    var dragGesture: some Gesture {
        DragGesture()
            .onChanged { value in
                if isMode {
                    drag_scroll(size: value.translation)
                    handLocation = value.location // 指し手アイコンを表示する場所
                }
                else {
                    var point = value.location
                    point = convertImagePosition(viewPoint: point, imageOffset: lastOffset)
                    print("x: \(point.x) y: \(point.y)" )  // 画像上の座標
                }
            }
            .onEnded {_ in 
                if isMode {
                    lastOffset = offset
                }
                else {
                    // 最終処理
                }
            }
    }
    
    func convertImagePosition( viewPoint:CGPoint, imageOffset: CGSize ) -> CGPoint {
        var point = viewPoint
        point.x = ( point.x  - imageOffset.width ) / currentScale
        point.y = ( point.y  - imageOffset.height ) / currentScale
        return point
    }
    
    var body: some View {
        ZStack(alignment: .leading) {
            GeometryReader { geometry in 
                Image(uiImage: image!)
                    .resizable()

                    .frame( width: image!.size.width * currentScale, height: image!.size.height * currentScale )
                    .background(GeometryReader {value in 
                        Color.clear
                            .onAppear{
                                viewSize = geometry.size
                            }
                    })
                    .offset(offset)
                    .gesture(dragGesture)
                    .gesture(TapGesture(count: 2) // ダブルタップでドラッグでの処理を切り替える
                        .onEnded() {_ in
                            isMode = !isMode
                        }
                    )

                Image(systemName: "hand.point.up") // 指し手アイコン、スクロールモードの時に表示する
                    .font(.largeTitle)
                    .foregroundColor(.white)
                    .position(handLocation)
                    .opacity(isMode ? 1: 0) // 透明度を変化して表示、非表示を切り替える
            }
        }
    }


  • X
  • Facebookでシェアする
  • はてなブックマークに追加する
  • LINEでシェアする

【Swift P4.5.1】自作スクロールの画像座標の取得確認。

2024-10-14 18:25:26 | Swift iPadOS用

ズームの拡大率を変えて試したが画像の座標と大体合っている。


import SwiftUI

struct SublView: View {
    private var image = UIImage(named: "Test3") // アセットフォルダに用意JPG
    private var currentScale: CGFloat = 1.0 // 数値を変えて座標値を確認

    @State private var viewSize: CGSize = CGSize.zero
    // 中略
    
    var body: some View {
        VStack(alignment: .leading) {
            GeometryReader { geometry in 
                Image(uiImage: image!)
                    .resizable()

                    //中略

                    .onTapGesture() { value in
                        let point = convertImagePosition(viewPoint: value, imageOffset: lastOffset)
                        
                        print("x: \(point.x) y: \(point.y)" ) // 座標値を表示。確認後は消去
                    }
            }
        }
       // 省略
    }

    func convertImagePosition( viewPoint: CGPoint, imageOffset: CGSize ) -> CGPoint {
        var point = viewPoint
        point.x = ( point.x  - imageOffset.width ) / currentScale // 本当は、currentScaleも引数で渡したい。(手抜き)
        point.y = ( point.y  - imageOffset.height ) / currentScale
        return point
    }

 


  • X
  • Facebookでシェアする
  • はてなブックマークに追加する
  • LINEでシェアする