背景:最近做一個(gè)游戲化的項(xiàng)目病曾,項(xiàng)目中會(huì)加載許多比較大的場(chǎng)景圖蹬刷。經(jīng)過(guò)測(cè)試一張9000*7000高清的png場(chǎng)景圖加載到內(nèi)存會(huì)消耗200M以上的內(nèi)存空間,就這一點(diǎn)就是開(kāi)發(fā)所不可接受的。還有什么加載時(shí)間長(zhǎng)鳄虱、卡頓等問(wèn)題就不說(shuō)了。
接下來(lái)介紹一下我的優(yōu)化歷程:
1凭峡、當(dāng)時(shí)趕項(xiàng)目進(jìn)度拙已,我就直接把圖片等比壓縮成4000*3111像素,內(nèi)存降到了50M摧冀,也就暫且接受了倍踪。
缺點(diǎn):大家都清楚了,圖片清晰度得不到保證了索昂,還需要進(jìn)行縮放建车。
2、我直接交UI幫我把PNG圖片轉(zhuǎn)換成了JPG椒惨,這樣圖片大小得到了縮減缤至,而且加載到內(nèi)存中也消耗很少的內(nèi)存資源。對(duì)于圖片透明度等無(wú)要求的需求框产,這樣也是一種不錯(cuò)的方式凄杯。
缺點(diǎn):不適合需要保留圖片透明度的場(chǎng)景。
3秉宿、我把大圖用PS拆分成了54張小圖戒突。在scrollViewDidScroll代理方法中計(jì)算當(dāng)前應(yīng)該顯示的圖片,然后手動(dòng)釋放掉不需要顯示的圖片描睦。這種方式內(nèi)存消耗確實(shí)非常小膊存,而且加載的是PNG圖片。
缺點(diǎn):滑動(dòng)時(shí)scrollViewDidScroll方法調(diào)用非常頻繁忱叭,所以會(huì)非常頻繁的遍歷這些圖片哪些會(huì)顯示隔崎,哪些需要釋放,頻繁的讀寫文件也帶來(lái)了性能問(wèn)題韵丑。
注:以上三種方式均通過(guò)UIImage.init(contentsOfFile: filePath)方式加載圖片(保證內(nèi)存可以及時(shí)的釋放掉)
相關(guān)代碼如下:
let deviceWidth: CGFloat = UIScreen.main.bounds.width
let deviceHeight: CGFloat = UIScreen.main.bounds.height
func initFrameView() {
self.scrollView.frame = CGRect(x: 0, y: 0, width: deviceWidth, height: deviceHeight)
//設(shè)置滑動(dòng)范圍
self.scrollView.contentSize = CGSize(width: 9514, height: 6388)
//設(shè)置最大縮放比例
self.scrollView.maximumZoomScale = 1.0
//設(shè)置最小縮放比例
self.scrollView.minimumZoomScale = 0.3
//關(guān)閉縮放反彈
self.scrollView.bouncesZoom = false
//關(guān)閉遇邊框反彈
self.scrollView.bounces = false
self.scrollView.delegate = self
self.view.addSubview(self.scrollView)
imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: 9514, height: 6388))
self.scrollView.addSubview(imageView)
for i in 0..<54 { //拆分成小圖片的張數(shù)
let imageV = UIImageView.init(frame: CGRect(x: CGFloat(i%6)*imageWidth, y: CGFloat(i/9)*imageHeight, width: imageWidth, height: imageHeight))
imageV.tag = i+1
imageView.addSubview(imageV)
}
}
UIScrollViewDelegate的代理方法:
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return imageView //縮放的View
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let views = self.imageView.subviews //取出UIImageView上的所有小塊UIimageView
for view in views {
if view is UIImageView {
//通過(guò)該方法判斷這個(gè)View是否在UIScrollView的當(dāng)前可視范圍內(nèi)
let contain = self.calculateFrame(view: view)
let imageV = view as! UIImageView
if contain == true { //如果在就顯示出來(lái)
//根據(jù)該View的tag值找到它對(duì)應(yīng)該顯示的圖片進(jìn)行顯示
self.imageOfFile(tag: imageV.tag, imageV: imageV)
} else { //不在就釋放掉
imageV.image = nil
}
}
}
}
func imageOfFile(tag:Int,imageV:UIImageView) {
DispatchQueue.global().async { //在子線程中進(jìn)行文件的讀取操作
let filePath = Bundle.main.path(forResource: String.init(format: "image_%d",tag), ofType: "jpg")!
DispatchQueue.main.async { //在主線程中進(jìn)行顯示操作
imageV.image = UIImage.init(contentsOfFile: filePath)
}
}
}
//判斷該view是否在scrollView的可視范圍內(nèi)
func calculateFrame(view:UIView) -> Bool {
let scrollX = self.scrollView.contentOffset.x
let scrollY = self.scrollView.contentOffset.y
let scale = self.scrollView.zoomScale //scrollView當(dāng)前的縮放系數(shù)
if fabsf(Float((scrollX+deviceWidth/2) - view.center.x*scale)) <= Float(view.width/2*scale + deviceWidth/2) &&
fabsf(Float((scrollY+deviceHeight/2)-view.center.y*scale)) <= Float(view.height/2*scale + deviceHeight/2) {
return true
}
return false
}
4爵卒、最后發(fā)現(xiàn)了一種更加簡(jiǎn)便的方式來(lái)顯示大圖。-----CATiledLayer
蘋果推薦的大圖顯示方式撵彻,API會(huì)自動(dòng)把大圖拆分成若干小貼片钓株,在需要顯示的時(shí)候才會(huì)進(jìn)行加載实牡。自帶漸入的效果。
缺點(diǎn):滑動(dòng)的時(shí)候比較消耗CPU轴合,通過(guò)CPU操作減小了內(nèi)存的損耗创坞。
func addTiledLayer() {
scrollView = UIScrollView.init(frame: self.view.bounds)
self.scrollView.contentSize = CGSize(width: 9514, height: 6388)
self.scrollView.maximumZoomScale = 1.0
self.scrollView.minimumZoomScale = 0.3
self.scrollView.bouncesZoom = false
self.scrollView.bounces = false
self.scrollView.delegate = self
self.view.addSubview(scrollView)
imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: 9514, height: 6388))
self.scrollView.addSubview(imageView)
tiledLayer.frame = CGRect(x: 0, y: 0, width: 9514, height: 6388)
tiledLayer.delegate = self
//tiledLayer.tileSize的默認(rèn)大小是256*256
scrollView.contentSize = tiledLayer.frame.size
imageView.layer.addSublayer(tiledLayer)
tiledLayer.setNeedsDisplay()
}
CALayerDelegate的代理方法:
//我使用PS把大圖切割成了950張小圖
func draw(_ layer: CALayer, in ctx: CGContext) {
let layer = layer as! CATiledLayer
let bounds = ctx.boundingBoxOfClipPath
let x:Int = Int(floor(bounds.origin.x / layer.tileSize.width))//列數(shù)
let y:Int = Int(floor(bounds.origin.y / layer.tileSize.height))//行數(shù)
//加載貼圖
let tileImage = UIImage.init(named: String.init(format: "test_%02ld.png",38*y+x)) //38是大圖切割的最大列數(shù)
UIGraphicsPushContext(ctx)
tileImage?.draw(in: bounds)
UIGraphicsPopContext()
}
我通過(guò)PS進(jìn)行的圖片切割,當(dāng)然也可以通過(guò)代理來(lái)實(shí)現(xiàn)受葛,然后從沙盒中把圖片拷貝出來(lái),代碼如下:
//切圖保存在沙河中
func cutImageAndSave() {
let filePath = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory, FileManager.SearchPathDomainMask.userDomainMask, true).last
let imageName = String.init(format: "%@/test-00-00.png", filePath!)
let tileImage = UIImage.init(contentsOfFile: imageName)
if tileImage != nil {
return
}
let image = UIImage.init(named: "city") //大圖
let imageV = UIImageView.init(image: image)
let WH:CGFloat = imageWidth
let HH:CGFloat = imageHeight
let size = image?.size
//ceil 向上取整
let rows:Int = Int(ceil((size?.height)! / HH))
let cols:Int = Int(ceil((size?.width)! / WH))
for y in 0..<rows {
for x in 0..<cols {
let subImage = self.captureView(theView: imageV, fra: CGRect(x: x*Int(WH), y: y*Int(HH), width: Int(WH), height: Int(HH)))
let path = String.init(format: "%@/test-%02ld-%02ld.png", filePath!,x,y)
do {
try UIImagePNGRepresentation(subImage)?.write(to: URL.init(fileURLWithPath: path))
} catch {
print("error")
}
}
}
}
//切圖
func captureView(theView:UIView, fra:CGRect) -> UIImage {
UIGraphicsBeginImageContext(theView.frame.size)
let context = UIGraphicsGetCurrentContext()
theView.layer.render(in: context!)
//獲取圖片
let img = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
let ref = img?.cgImage?.cropping(to: fra)
let i = UIImage.init(cgImage: ref!)
return i
}
如有不對(duì)的地方歡迎指正题涨。