一区二区三区在线-一区二区三区亚洲视频-一区二区三区亚洲-一区二区三区午夜-一区二区三区四区在线视频-一区二区三区四区在线免费观看

服務器之家:專注于服務器技術及軟件下載分享
分類導航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術|正則表達式|C/C++|IOS|C#|Swift|Android|VB|R語言|JavaScript|易語言|vb.net|

服務器之家 - 編程語言 - Swift - 如何利用SwiftUI實現可縮放的圖片預覽器

如何利用SwiftUI實現可縮放的圖片預覽器

2021-12-24 14:13Lebron Swift

這篇文章主要給大家介紹了關于如何利用SwiftUI實現可縮放圖片預覽器的相關資料,文中通過示例代碼介紹的非常詳細,對大家學習或者使用SwiftUI具有一定的參考學習價值,需要的朋友可以參考下

前言

在開發中,我們經常會遇到點擊圖片查看大圖的需求。在 Apple 的推動下,iOS 開發必定會從 UIKit 慢慢向 SwiftUI 轉變。為了更好地適應這一趨勢,今天我們用 SwiftUI 實現一個可縮放的圖片預覽器。

實現過程

程序的初步構想

要做一個程序,首先肯定是給它起個名字。既然是圖片預覽器(Image Previewer),再加上我自己習慣用的前綴 LBJ,就把它命名為 LBJImagePreviewer 吧。

既然是圖片預覽器,所以需要外部提供圖片給我們;然后是可縮放,所以需要一個最大的縮放倍數。有了這些思考,可以把 LBJImagePreviewer 簡單定義為:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import SwiftUI
 
public struct LBJImagePreviewer: View {
 
  private let uiImage: UIImage
  private let maxScale: CGFloat
 
  public init(uiImage: UIImage, maxScale: CGFloat = LBJImagePreviewerConstants.defaultMaxScale) {
    self.uiImage = uiImage
    self.maxScale = maxScale
  }
 
  public var body: some View {
    EmptyView()
  }
}
 
public enum LBJImagePreviewerConstants {
  public static let defaultMaxScale: CGFloat = 16
}

在上面代碼中,給 maxScale 設置了一個默認值。

另外還可以看到 maxScale 的默認值是通過 LBJImagePreviewerConstants.defaultMaxScale 來設置的,而不是直接寫 16,這樣做的目的是把代碼中用到的數值和經驗值等整理到一個地方,方便后續的修改。這是一個好的編程習慣。

細心的讀者可能還會注意到 LBJImagePreviewerConstants 是一個 enum 類型。為什么不用 struct 或者 class 呢?

在 Swift 中定義靜態方法,class / struct / enum 三者如何選擇?

在開發過程中,我們經常會遇到需要定義一些靜態方法的需求。通常我們會想到用 class 和 struct 去定義,然而卻忽略了 enum 也可以擁有靜態方法。那么問題來了:既然三者都可以定義靜態方法,那么我們應該如何選擇?
下面直接給出答案:

  • class:class 是引用類型,支持繼承。如果你需要這兩個特性,那么選擇 class。
  • struct:struct 是值類型,不支持繼承。如果你需要值類型,并且某些時候需要這個類型的實例,那么用 struct。
  • enum:enum 也是值類型,一般用來定義一組相關的值。如果我們想要的靜態方法是一系列的工具,不需要任何的實例化和繼承,那么用 enum 最合適。

另外,其實這個規則也適用于靜態變量。

顯示 UIImage

當用戶點開圖片預覽器,當然是希望圖片等比例占據整個圖片預覽器,所以需要知道圖片預覽器當前的尺寸和圖片尺寸,從而通過計算讓圖片等比例占據整個圖片預覽器。

圖片預覽器當前的尺寸可以通過 GeometryReader 得到;圖片大小可以直接從 UIImage 得到。所以我們可以把

LBJImagePreviewer 的 body 定義如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public struct LBJImagePreviewer: View {
  public var body: some View {
    GeometryReader { geometry in                  // 用于獲取圖片預覽器所占據的尺寸
      let imageSize = imageSize(fits: geometry)   // 計算圖片等比例鋪滿整個預覽器時的尺寸
      ScrollView([.vertical, .horizontal]) {
        imageContent
          .frame(
            width: imageSize.width,
            height: imageSize.height
          )
          .padding(.vertical, (max(0, geometry.size.height - imageSize.height) / 2))  // 讓圖片在預覽器垂直方向上居中
      }
      .background(Color.black)
    }
    .ignoresSafeArea()
  }
}
 
private extension LBJImagePreviewer {
  var imageContent: some View {
    Image(uiImage: uiImage)
      .resizable()
      .aspectRatio(contentMode: .fit)
  }
 
  /// 計算圖片等比例鋪滿整個預覽器時的尺寸
  func imageSize(fits geometry: GeometryProxy) -> CGSize {
      let hZoom = geometry.size.width / uiImage.size.width
      let vZoom = geometry.size.height / uiImage.size.height
      return uiImage.size * min(hZoom, vZoom)
  }
}
 
extension CGSize {
  /// CGSize 乘以 CGFloat
  static func * (lhs: Self, rhs: CGFloat) -> CGSize {
    CGSize(width: lhs.width * rhs, height: lhs.height * rhs)
  }
}

這樣我們就把圖片用 ScrollView 顯示出來了。

雙擊縮放

想要 ScrollView 的內容可以滾動起來,必須要讓它的尺寸大于 ScrollView 的尺寸。沿著這個思路可以想到,我們可修改 imageContent 的大小來實現放大縮小,也就是修改下面這個 frame:

?
1
2
3
4
5
imageContent
  .frame(
    width: imageSize.width,
    height: imageSize.height
  )

我們通過用 imageSize(fits: geometry) 的返回值乘以一個倍數,就可以改變 frame 的大小。這個倍數就是放大的倍數。因此我們定義一個變量記錄倍數,然后通過雙擊手勢改變它,就能把圖片放大縮小,有變動的代碼如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// 當前的放大倍數
@State
private var zoomScale: CGFloat = 1
 
public var body: some View {
  GeometryReader { geometry in
    let zoomedImageSize = zoomedImageSize(fits: geometry)
    ScrollView([.vertical, .horizontal]) {
      imageContent
        .gesture(doubleTapGesture())
        .frame(
          width: zoomedImageSize.width,
          height: zoomedImageSize.height
        )
        .padding(.vertical, (max(0, geometry.size.height - zoomedImageSize.height) / 2))
    }
    .background(Color.black)
  }
  .ignoresSafeArea()
}
 
// 雙擊手勢
func doubleTapGesture() -> some Gesture {
  TapGesture(count: 2)
    .onEnded {
      withAnimation {
        if zoomScale > 1 {
          zoomScale = 1
        } else {
          zoomScale = maxScale
        }
      }
    }
}
 
// 縮放時圖片的大小
func zoomedImageSize(fits geometry: GeometryProxy) -> CGSize {
  imageSize(fits: geometry) * zoomScale
}

放大手勢縮放

放大手勢縮放的原理與雙擊一樣,都是想辦法通過修改 zoomScale 來達到縮放圖片的目的。SwiftUI 中的放大手勢是 MagnificationGesture。代碼變動如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// 穩定的放大倍數,放大手勢以此為基準來改變 zoomScale 的值
@State
private var steadyStateZoomScale: CGFloat = 1
 
// 放大手勢縮放過程中產生的倍數變化
@GestureState
private var gestureZoomScale: CGFloat = 1
 
// 變成了只讀屬性,當前圖片被放大的倍數
var zoomScale: CGFloat {
  steadyStateZoomScale * gestureZoomScale
}
 
func zoomGesture() -> some Gesture {
  MagnificationGesture()
    .updating($gestureZoomScale) { latestGestureScale, gestureZoomScale, _ in
      // 縮放過程中,不斷地更新 `gestureZoomScale` 的值
      gestureZoomScale = latestGestureScale
    }
    .onEnded { gestureScaleAtEnd in
      // 手勢結束,更新 steadyStateZoomScale 的值;
      // 此時 gestureZoomScale 的值會被重置為初始值 1
      steadyStateZoomScale *= gestureScaleAtEnd
      makeSureZoomScaleInBounds()
    }
}
 
// 確保放大倍數在我們設置的范圍內;Haptics 是加上震動效果
func makeSureZoomScaleInBounds() {
  withAnimation {
    if steadyStateZoomScale < 1 {
      steadyStateZoomScale = 1
      Haptics.impact(.light)
    } else if steadyStateZoomScale > maxScale {
      steadyStateZoomScale = maxScale
      Haptics.impact(.light)
    }
  }
}
 
// Haptics.swift
enum Haptics {
  static func impact(_ style: UIImpactFeedbackGenerator.FeedbackStyle) {
    let generator = UIImpactFeedbackGenerator(style: style)
    generator.impactOccurred()
  }
}

到目前為止,我們的圖片預覽器就實現了。是不是很簡單?

延伸 · 閱讀

精彩推薦
  • SwiftSwift使用CollectionView實現廣告欄滑動效果

    Swift使用CollectionView實現廣告欄滑動效果

    這篇文章主要為大家詳細介紹了Swift使用CollectionView實現廣告欄滑動效果,具有一定的參考價值,感興趣的小伙伴們可以參考一下...

    Stevin的技術博客12372021-01-13
  • SwiftSwift的74個常用內置函數介紹

    Swift的74個常用內置函數介紹

    這篇文章主要介紹了Swift的74個常用內置函數介紹,這篇文章列舉出了所有的Swift庫函數,內置函數是指無需引入任何模塊即可以直接使用的函數,需要的朋友可...

    Swift教程網5802020-12-19
  • Swiftswift where與匹配模式的實例詳解

    swift where與匹配模式的實例詳解

    這篇文章主要介紹了swift where與匹配模式的實例詳解的相關資料,這里附有簡單的示例代碼,講的比較清楚,需要的朋友可以參考下...

    追到夢的魔術師14382021-01-06
  • Swiftmac git xcrun error active developer path 錯誤

    mac git xcrun error active developer path 錯誤

    本文主要是講訴了如何解決在mac下使用git;xcode4.6的環境時,出現了錯誤(mac git xcrun error active developer path)的解決辦法,希望對大家有所幫助...

    Swift教程網2232020-12-16
  • SwiftSwift能代替Objective-C嗎?

    Swift能代替Objective-C嗎?

    這是我在網上上看到的答案,復制粘貼過來和大家分享一下,因為我和很多人一樣很關心Swift的出現對Mac開發的影響和對Objective-C的影響。...

    Swift教程網4412020-12-16
  • SwiftSwift實現多個TableView側滑與切換效果

    Swift實現多個TableView側滑與切換效果

    這篇文章主要為大家詳細介紹了Swift實現多個TableView側滑與切換效果,具有一定的參考價值,感興趣的小伙伴們可以參考一下...

    乞力馬扎羅的雪雪5822021-01-08
  • SwiftSwift中轉義閉包示例詳解

    Swift中轉義閉包示例詳解

    在Swift 中的閉包類似于結構塊,并可以在任何地方調用,下面這篇文章主要給大家介紹了關于Swift中轉義閉包的相關資料,需要的朋友可以參考下...

    小小小_小朋友11412021-12-26
  • SwiftSwift教程之基礎數據類型詳解

    Swift教程之基礎數據類型詳解

    這篇文章主要介紹了Swift教程之基礎數據類型詳解,本文詳細講解了Swift中的基本數據類型和基本語法,例如常量和變量、注釋、分號、整數、數值類型轉換等...

    Swift教程網5162020-12-18
主站蜘蛛池模板: 欧美办公室激情videos高清 | 动漫美丽妇人1~2在线看 | a天堂视频| 手机能看的黄色网站 | 国产成人盗拍精品免费视频 | 国产精品手机视频一区二区 | 欧美伊人久久久久久久久影院 | 亚洲人成网站在线观看青青 | 日韩夫妻性生活 | 日韩xx00| 欧美一级xxxx俄罗斯一级 | 青青草影院在线观看 | 免费av在线视频 | 亚洲精品卡一卡2卡3卡4卡 | 国产综合成人亚洲区 | 精品久久久久久 | 天仙tv微福视频 | 精品国产自在现线拍400部 | 午夜爱爱爱爱爽爽爽视频网站 | 国产精品xxxav免费视频 | 四虎在线最新永久免费 | 欧美bbxx | 欧美一区不卡二区不卡三区 | 青草青青在线视频 | 国产区最新 | 久久99精品国产免费观看 | 暖暖中国免费观看高清完整版 | 亚洲精品福利你懂 | 18videossex性欧美69 | 四虎影院在线免费观看视频 | 亚洲国产精品网 | 国内精品一区二区三区东京 | 秘书在办公室疯狂被hd | 精品一区二区三区中文 | 国产区久久 | 洗濯屋し在线观看 | 无颜之月5集全免费看无删除 | 日本阿v精品视频在线观看 日本xxx片免费高清在线 | 四虎最新永久免费视频 | 青苹果乐园影院在线播放 | 俄罗斯女人与公拘i交酡 |