swift - 在 SwiftUI 中使用图像和 PageView 内存不足
问题描述
我正在编写一个应用程序,该应用程序向用户显示可以浏览和放大的大图片,类似于照片应用程序或书籍。我正在尝试用 SwiftUI 编写这个。我一直在关注苹果网站上的 SwiftUI 教程, 并且我已经修改了代码以读取 json 文件以创建“Imageslides”数组 - 一种保存图像和有关它们的信息的方法。然后,我使用 PageView 和 PageViewController 来显示 ContentView> ContentView 显示图像和一些按钮,这些按钮显示关于我正在显示的图像的文本。我的问题是当我浏览页面时,内存会不断增加,直到应用程序崩溃。现在,如果我使用 UIKit,我可以使用
if let imgPath = Bundle.main.path(forResource: name, ofType: nil)
{
return UIImage(contentsOfFile: imgPath)
}
但是我看不到如何在swiftUI中使用图像来使用contentsOfFile,或者在不需要时让图像超出内存的另一种方式。我将图像放在资源文件夹中,而不是在资产库中。谢谢你的帮助。
//data.swift - reading from json, mainly from apple's tutorials
import Foundation
import UIKit
import SwiftUI
let imageData: [Imageslide] = load("imageData.json")
func load<T: Decodable>(_ filename: String) -> T {
let data: Data
guard let file = Bundle.main.url(forResource: filename, withExtension: nil)
else {
fatalError("Couldn't find \(filename) in main bundle.")
}
do {
data = try Data(contentsOf: file)
} catch {
fatalError("Couldn't load \(filename) from main bundle:\n\(error)")
}
do {
let decoder = JSONDecoder()
return try decoder.decode(T.self, from: data)
} catch {
fatalError("Couldn't parse \(filename) as \(T.self):\n\(error)")
}
}
final class ImageStore {
typealias _ImageDictionary = [String: CGImage]
fileprivate var images: _ImageDictionary = [:]
fileprivate static var scale = 2
static var shared = ImageStore()
func image(name: String) -> Image {
let index = _guaranteeImage(name: name)
return Image(images.values[index], scale: CGFloat(ImageStore.scale), label: Text(name))
}
static func loadImage(name: String) -> CGImage {
guard
let url = Bundle.main.url(forResource: name, withExtension: "png"),
let imageSource = CGImageSourceCreateWithURL(url as NSURL, nil),
let image = CGImageSourceCreateImageAtIndex(imageSource, 0, nil)
else {
fatalError("Couldn't load image \(name).jpg from main bundle.")
}
return image
}
fileprivate func _guaranteeImage(name: String) -> _ImageDictionary.Index {
if let index = images.index(forKey: name) { return index }
images[name] = ImageStore.loadImage(name: name)
return images.index(forKey: name)!
}
}
//imageslide.swift - abstract for holding my image slide and button info
import SwiftUI
struct buttonInfo: Hashable, Codable{
var text: String? = nil
var coords: [Int]? = nil
public enum CodingKeys : String, CodingKey {
case text, coords
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.text = try container.decodeIfPresent(String.self, forKey: .text)
self.coords = try container.decodeIfPresent([Int].self, forKey: .coords)
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.text, forKey: .text)
try container.encode(self.coords, forKey: .coords)
}
}
struct Imageslide: Hashable, Codable, Identifiable {
public var imagename: String
public var id: Int
public var noButtons: Int
public var buttoninfo: [buttonInfo]? = nil
public enum CodingKeys : String, CodingKey {
case imagename, id, noButtons, buttoninfo
case imageslide = "Imageslide"
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let imageslide = try container.nestedContainer(keyedBy:
CodingKeys.self, forKey: .imageslide)
self.imagename = try imageslide.decode(String.self, forKey: .imagename)
self.id = try imageslide.decode(Int.self, forKey: .id)
self.noButtons = try imageslide.decode(Int.self, forKey: .id)
self.buttoninfo = try imageslide.decodeIfPresent([buttonInfo].self, forKey: .buttoninfo)
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
var imageslide = container.nestedContainer(keyedBy: CodingKeys.self, forKey: .imageslide)
try imageslide.encode(self.imagename, forKey: .imagename)
try imageslide.encode(self.id, forKey: .id)
try imageslide.encode(self.noButtons, forKey: .noButtons)
try imageslide.encode(self.buttoninfo, forKey: .buttoninfo)
}
}
extension Imageslide {
var image: Image {
ImageStore.shared.image(name: imagename)
}
}
struct ButtonDataStore: Codable{
var buttonData: [buttonInfo]
}
//contentview.swift - used to show the images and buttons
import SwiftUI
struct ContentView: View {
var imageslide: Imageslide
@State private var button1Visible = true
@State var scale: CGFloat = 1.0
var body: some View {
ZStack {
Image(uiImage: (UIImage(named: imageslide.imagename)!))
// imageslide.image
// Image(imageslide.imagename)
.resizable()
.frame(width: 2732/2, height: 2048/2)
//Pinch to zoom code goes here
.background(/*@START_MENU_TOKEN@*/Color.black/*@END_MENU_TOKEN@*/)
.edgesIgnoringSafeArea(.all)
.statusBar(hidden: true)
if (imageslide.noButtons != 0)
{
if imageslide.buttoninfo != nil
{
ForEach(imageslide.buttoninfo!, id: \.self) {count in
ButtonView(text: count.text!, xcoord: count.coords![0], ycoord: count.coords![1], xcoordo: count.coords![2], ycoordo: count.coords![3])
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
//ContentView(imageslide: imageData[0])
ContentView(imageslide: imageData[1]).previewLayout(
.fixed(width: 2732/2, height: 2048/2)
)
}
}
}
//PageViewController.swift
import SwiftUI
import UIKit
struct PageViewController: UIViewControllerRepresentable {
var controllers: [UIViewController]
@Binding var currentPage: Int
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func makeUIViewController(context: Context) -> UIPageViewController {
let pageViewController = UIPageViewController(
transitionStyle: .scroll,
navigationOrientation: .horizontal)
pageViewController.dataSource = context.coordinator
pageViewController.delegate = context.coordinator
return pageViewController
}
func updateUIViewController(_ pageViewController: UIPageViewController, context: Context) {
pageViewController.setViewControllers(
[controllers[currentPage]], direction: .forward, animated: true)
}
class Coordinator: NSObject, UIPageViewControllerDataSource, UIPageViewControllerDelegate {
var parent: PageViewController
init(_ pageViewController: PageViewController) {
self.parent = pageViewController
}
func pageViewController(
_ pageViewController: UIPageViewController,
viewControllerBefore viewController: UIViewController) -> UIViewController?
{
guard let index = parent.controllers.firstIndex(of: viewController) else {
return nil
}
if index == 0 {
return parent.controllers.last
}
return parent.controllers[index - 1]
}
func pageViewController(
_ pageViewController: UIPageViewController,
viewControllerAfter viewController: UIViewController) -> UIViewController?
{
guard let index = parent.controllers.firstIndex(of: viewController) else {
return nil
}
if index + 1 == parent.controllers.count {
return parent.controllers.first
}
return parent.controllers[index + 1]
}
func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
if completed,
let visibleViewController = pageViewController.viewControllers?.first,
let index = parent.controllers.firstIndex(of: visibleViewController)
{
parent.currentPage = index
}
}
}
}
//pageview.swift
import SwiftUI
struct PageView<Page: View>: View {
var viewControllers: [UIHostingController<Page>]
@State var currentPage = 0
init(_ views: [Page]) {
self.viewControllers = views.map { UIHostingController(rootView: $0) }
}
var body: some View {
PageViewController(controllers: viewControllers, currentPage: $currentPage)
}
}
struct PageView_Previews: PreviewProvider {
static var previews: some View {
PageView(imageData.map {ContentView(imageslide: $0)})
}
}
我正在使用的 JSON 示例。
{
"Imageslide":{
"imagename":"MOS_SHB_1",
"id":1001,
"noButtons":0
}
},
{
"Imageslide":{
"imagename":"MOS_SHB_2",
"id":1002,
"noButtons":0
}
},
{
"Imageslide":{
"imagename":"MOS_SHB_3",
"id":1003,
"noButtons":1,
"buttoninfo":[
{
"text":"The two halves of the arch touched for the first time. Workers riveted both top and bottom sections of the arch together, and the arch became self-supporting, allowing the support cables to be removed. On 20 August 1930 the joining of the arches was celebrated by flying the flags of Australia and the United Kingdom from the jibs of the creeper cranes.",
"coords":[
-150,
220,
200,
200
]
}
]
}
},
解决方案
UIImage
像以前一样创建
let imageModel = UIImage(contentsOfFile: imgPath)
并将其body
用作Image
视图模型
Image(uiImage: imageModel ?? UIImage())
推荐阅读
- spring - 我想在每天早上 8:00 和下午 3:10 使用 Scheduled in Spring 执行 Job
- jquery - 将类的子类克隆到类中
- amazon-web-services - 如何在应用程序流程中将服务从 CF 连接到 AWS 中的应用程序?
- python - 删除所有非字符,除了python中的数字、拉丁字母和西里尔字母
- macos - Cocoa swift macOS 打印 html 到 pdf
- android - 如何检查 Unity 应用程序是否在修改后的 Android 系统上运行?
- java - 如何使用 system.out.println() 使 Wildfly 控制台打印日文文本;
- java - LibGDX box2d 检测对象的特定实例
- swift - 条件绑定的初始化程序必须具有可选类型,而不是“字符串”?
- dart - Flutter 错误 - 内核二进制格式版本无效(找到 4,预期 6)