Доброго времени суток! Господа, такой вопрос: работаю с большим количеством картинок, порядка 100-200, в перспективе ещё больше. И вот когда загружаю их все в CollectionView, происходит колоссальный жор оперативной памяти, доходит до 3 Гб. При закрытии окна все становится нормально. Загружаю из галереи, используя путь до картинки, который хранится в CoreData, т.к. я беру только то, что мне нужно. Есть ли какие-нибудь способы обработки такого количества картинок? Спасибо.
Работа с большим количеством картинок
Может быть по мере перезаписи ячеек догружать следующую партию? Ну и для этого нужно использовать очередь.
Я делал подгрузку фоток с измененным качеством и кол-во ячеек в строке было 3.
class UIViewController {
@IBOutlet weak var uiPhotoCollectionView: UICollectionView!
var allPhotos: PHFetchResult<PHAsset>!
override func viewDidLoad() {
super.viewDidLoad()
PHPhotoLibrary.shared().register(self)
uiPhotoCollectionView.register(CustomViewCell.nib,
forCellWithReuseIdentifier: CustomViewCell.cellIdentifier)
}
deinit {
PHPhotoLibrary.shared().unregisterChangeObserver(self)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
Permissions.authorisationStatus(attachmentTypeEnum: .photoLibrary, vc: self) { (status) in
if status {
if self.allPhotos == nil {
// Create a PHFetchResult object for each section in the table view.
DispatchQueue.global().async {
let allPhotosOptions = PHFetchOptions()
allPhotosOptions.predicate = NSPredicate(format: "mediaType = %d",
PHAssetMediaType.image.rawValue)
allPhotosOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]
self.allPhotos = PHAsset.fetchAssets(with: .image, options: allPhotosOptions)
DispatchQueue.main.async {
self.uiPhotoCollectionView.reloadData()
}
}
}
}
}
}
}
extension UIViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return allPhotos == nil ? 0 : allPhotos.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let asset = allPhotos.object(at: indexPath.item)
guard let layoutAttributes = collectionView.collectionViewLayout.layoutAttributesForItem(at: indexPath),
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: CustomViewCell.cellIdentifier, for: indexPath) as? CustomViewCellelse { return UICollectionViewCell() }
cell.indexPath = indexPath
cell.photoAsset = asset
cell.size = layoutAttributes.frame.size
return cell
}
}
extension UIViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 0.5
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 0.5
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let width = collectionView.frame.width
let photosCountInRow = 3
return CGSize(width: width/photosCountInRow - 0.5, height: width/photosCountInRow - 0.5)
}
}
extension UIViewController: PHPhotoLibraryChangeObserver {
func photoLibraryDidChange(_ changeInstance: PHChange) {
guard let changes = changeInstance.changeDetails(for: allPhotos) else { return }
// Change notifications may be made on a background queue. Re-dispatch to the
// main queue before acting on the change as we'll be updating the UI.
DispatchQueue.main.sync {
// Hang on to the new fetch result.
allPhotos = changes.fetchResultAfterChanges
// to avoid such errors we should just call reloadData()
// Fatal Exception: NSInternalInconsistencyException
// attempt to delete and reload the same index path
// (<NSIndexPath: 0x1d0431720> {length = 2, path = 0 - 1})
self.uiPhotoCollectionView?.reloadData()
/*
if changes.hasIncrementalChanges {
// If we have incremental diffs, animate them in the collection view.
guard let collectionView = self.uiPhotoCollectionView else { fatalError() }
collectionView.performBatchUpdates({
// For indexes to make sense, updates must be in this order:
// delete, insert, reload, move
if let removed = changes.removedIndexes, removed.count > 0 {
collectionView.deleteItems(at: removed.map({ IndexPath(item: $0, section: 0) }))
}
if let inserted = changes.insertedIndexes, inserted.count > 0 {
collectionView.insertItems(at: inserted.map({ IndexPath(item: $0, section: 0) }))
}
if let changed = changes.changedIndexes, changed.count > 0 {
collectionView.reloadItems(at: changed.map({ IndexPath(item: $0, section: 0) }))
}
changes.enumerateMoves { fromIndex, toIndex in
collectionView.moveItem(at: IndexPath(item: fromIndex, section: 0),
to: IndexPath(item: toIndex, section: 0))
}
})
} else {
// Reload the collection view if incremental diffs are not available.
uiPhotoCollectionView!.reloadData()
}
*/
}
}
}
class CustomViewCell: UICollectionViewCell {
static let nib = UINib(nibName: "CustomViewCell", bundle: Bundle.main)
static let cellIdentifier = "CustomViewCell"
private let scale = UIScreen.main.scale
var photoAsset: PHAsset? {
didSet {
loadPhotoAssetIfNeeded()
}
}
var size: CGSize? {
didSet {
loadPhotoAssetIfNeeded()
}
}
var indexPath: IndexPath?
private var imageRequestID: PHImageRequestID?
@IBOutlet weak var uiPhotoView: UIImageView!
override func prepareForReuse() {
super.prepareForReuse()
uiPhotoView.image = nil
//Cancel requests if needed
let manager = PHImageManager.default()
guard let imageRequestID = self.imageRequestID else { return }
manager.cancelImageRequest(imageRequestID)
self.imageRequestID = nil
}
override func awakeFromNib() {
uiPhotoLabel.layer.cornerRadius = uiPhotoLabel.frame.height / 2
uiPhotoLabel.layer.borderWidth = 1
uiPhotoLabel.layer.borderColor = UIColor.white.cgColor
uiPhotoLabel.layer.masksToBounds = true
}
private func loadPhotoAssetIfNeeded() {
guard let indexPath = self.indexPath,
let asset = photoAsset, let size = self.size else { return }
let options = PHImageRequestOptions()
options.deliveryMode = .highQualityFormat
options.resizeMode = .fast
options.isSynchronous = false
options.isNetworkAccessAllowed = true
let manager = PHImageManager.default()
let newSize = CGSize(width: size.width * scale,
height: size.height * scale)
imageRequestID = manager.requestImage(for: asset, targetSize: newSize, contentMode: .aspectFill, options: options, resultHandler: { [unowned self] (result, _) in
guard self.indexPath?.item == indexPath.item else { return }
self.imageRequestID = nil
UIView.transition(with: self.uiPhotoView,
duration: 0.3,
options: .transitionCrossDissolve,
animations: { self.uiPhotoView.set(image: result, focusOnFaces: false) },
completion: nil)
})
}
}
Спасибо большое за ответы, возникла небольшая проблема с идентификацией в двух местах, а именно в месте Permissions.authorisationStatus… и с переменной allPhotos, пишет что используются неизвестные идентификаторы. Буду благодарен за помощь в устранении проблем.
Пардон, класс Permissions был кастомный, забыл про него.
enum AttachmentType: String {
case camera, photoLibrary
}
public class Permissions {
static let alertForPhotoLibraryMessage = LocalizedString("msg_alert_for_photo_library")
static let alertForCameraAccessMessage = LocalizedString("msg_alert_for_camera_access")
static func authorisationStatus(attachmentTypeEnum: AttachmentType, vc: UIViewController, completion: @escaping (_ status: Bool) -> ()) {
switch attachmentTypeEnum {
case .camera:
cameraPermissionCheck(vc) { status in
DispatchQueue.main.async {
completion(status)
}
}
case .photoLibrary:
photoLibraryPermissionCheck(vc) { status in
DispatchQueue.main.async {
completion(status)
}
}
}
}
private static func photoLibraryPermissionCheck(_ vc: UIViewController, completion: @escaping (_ status: Bool) -> ()) {
let status = PHPhotoLibrary.authorizationStatus()
switch status {
case .authorized:
completion(true)
case .denied, .notDetermined, .restricted:
PHPhotoLibrary.requestAuthorization { status in
if status == PHAuthorizationStatus.authorized {
completion(true)
}
else {
completion(false)
addAlertForSettings(.photoLibrary, vc: vc)
}
}
}
}
private static func cameraPermissionCheck(_ vc: UIViewController, completion: @escaping (_ status: Bool) -> ()) {
let cameraMediaType = AVMediaTypeVideo
let status = AVCaptureDevice.authorizationStatus(forMediaType: cameraMediaType)
switch status {
case .authorized:
completion(true)
case .denied, .notDetermined, .restricted:
AVCaptureDevice.requestAccess(forMediaType: cameraMediaType) { granted in
if granted {
completion(true)
}
else {
completion(false)
addAlertForSettings(.camera, vc: vc)
}
}
}
}
private static func addAlertForSettings(_ attachmentTypeEnum: AttachmentType, vc: UIViewController) {
var alertTitle: String = ""
if attachmentTypeEnum == AttachmentType.camera{
alertTitle = alertForCameraAccessMessage
}
if attachmentTypeEnum == AttachmentType.photoLibrary{
alertTitle = alertForPhotoLibraryMessage
}
let cameraUnavailableAlertController = UIAlertController (title: nil , message: alertTitle, preferredStyle: .alert)
let settingsAction = UIAlertAction(title: LocalizedString("action_settings"), style: .destructive) { (_) -> Void in
let settingsUrl = NSURL(string: UIApplicationOpenSettingsURLString)
if let url = settingsUrl {
UIApplication.shared.open(url as URL)
}
}
let cancelAction = UIAlertAction(title: LocalizedString("action_cancel"), style: .default, handler: nil)
cameraUnavailableAlertController.addAction(cancelAction)
cameraUnavailableAlertController.addAction(settingsAction)
DispatchQueue.main.async {
vc.present(cameraUnavailableAlertController , animated: true, completion: nil)
}
}
}
LocalizedString замените на обычные сообщения.
А по поводу переменной allPhotos смотрите что у вас не так. Она определена в ViewController’e. Может вы не там поместили ее.
Спасибо. Я так понимаю, проблема с allPhotos в том, что она определена во VC, но мы же дописывали extension. AllPhoto будет видна в нем?
В итоге разобрался, но делал немножко другим способом. Теперь можно доставать картинки по их номеру в галерее (indexpath.row).
Такой вопрос: можно ли доставать из галереи картинки не по номеру, а по названию или их пути, и если да то как? Спасибо.