Im grabbing the photos from library using a cocoa pod called BSImagePicker. The Images are then stored into a photoArray. I want to take those images from the array and present them in an image view that's held inside the scrollView. However the images aren't showing up at all.
This is what I've currently tried.
This is the code that the cocoa pod uses to store images into the array:
func openImagePicker() {
let vc = BSImagePickerViewController()
self.bs_presentImagePickerController(
vc,
animated: true,
select: { (assest: PHAsset) -> Void in },
deselect: { (assest: PHAsset) -> Void in },
cancel: { (assest: [PHAsset]) -> Void in },
finish: { (assest: [PHAsset]) -> Void in
for i in 0..<assest.count {
self.SelectedAssets.append(assest[i])
}
self.convertAssetToImages()
},
completion: nil
)
print(photoArray)
setupImages(photoArray)
}
this is the code that converts asset to image
extension addImages {
func convertAssetToImages() -> Void {
if SelectedAssets.count != 0{
for i in 0..<SelectedAssets.count{
let manager = PHImageManager.default()
let option = PHImageRequestOptions()
var thumbnail = UIImage()
option.isSynchronous = true
let width = scrollView.frame.width
let height = scrollView.frame.height
manager.requestImage(for: SelectedAssets[i], targetSize: CGSize(width: width, height: height), contentMode: .aspectFill, options: option, resultHandler: {(result,info) -> Void in
thumbnail = result!
})
let data = thumbnail.jpegData(compressionQuality: 1)
let newImage = UIImage(data: data! as Data)
self.photoArray.append(newImage! as UIImage)
}
}
}
}
this code takes image from photo array into scrollview
func setupImages(_ images: [UIImage]){
for a in 0..<photoArray.count {
let theImage = UIImageView()
theImage.image = self.photoArray[a]
let xPosition = UIScreen.main.bounds.width * CGFloat(a)
self.scrollView.frame = CGRect(x: xPosition, y: 0, width: self.scrollView.frame.width, height: self.scrollView.frame.height)
theImage.contentMode = .scaleAspectFit
self.scrollView.contentSize.width = self.scrollView.frame.width * CGFloat(a + 1)
self.scrollView.addSubview(theImage)
}
}
I have no idea why it isn't working.
Related
I have a scroll view that has an image view embedded into it. The user clicks the "Take Photo" button, takes a picture, and then those photos are stored in an array and then displayed on the scrollable imageView. My issue is that after the didFinishSelectingMedia is called and the camera closes, the imageView always shows the first image in the array. I want the imageView to show the most recent image added to the array and scroll backward through the images until the user comes to the first image in the array. How do I make this happen?
Btw: Idk if this is even plausible, but I tried reversing the for loop and that didn't work.
I'm a new programmer, so please explain your answers.
Here's my code:
import UIKit
class ViewController: UIViewController, UINavigationControllerDelegate, UIImagePickerControllerDelegate {
#IBOutlet weak var scrollImageView: UIScrollView!
var imageTaken: [UIImage] = []
var imagePicker = UIImagePickerController()
override func viewDidLoad() {
super.viewDidLoad()
scrollImageView.frame = scrollImageView.frame
}
#IBAction func takePhotoButton(_ sender: Any) {
imagePicker = UIImagePickerController()
imagePicker.delegate = self
imagePicker.sourceType = .camera
present(imagePicker, animated: true, completion: nil)
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
imageTaken.append((info[.originalImage] as? UIImage)!)
imagePicker.dismiss(animated: true, completion: nil)
for i in 0..<imageTaken.count {
let imageView = UIImageView()
imageView.image = imageTaken[i]
imageView.contentMode = .scaleAspectFit
let xPosition = self.scrollImageView.frame.width * CGFloat(i)
imageView.frame = CGRect(x: xPosition, y: 0, width: self.scrollImageView.frame.width, height: self.scrollImageView.frame.height)
scrollImageView.contentSize.width = scrollImageView.frame.width * CGFloat(i + 1)
scrollImageView.addSubview(imageView)
}
}
}
Two options I guess you can take which ever one is seems easier.
OOP — Create an array of objects example:
struct TakePhotos {
var image : UIImage
var timestamp : Date
}
Then sort the array by the timestamp.
Use PHAsset
import Photos
Using PHAsset you should be able to retrieve the images taken from the user's album. Set a count when the user takes a new image.
Then you can run a query and sort the images in reverse order, they will already have the timestamp.
let photoOptions = PHFetchOptions()
photoOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]
This is what I did in my application.
// FIXME: - Query only happens once this needs to be refactored so it happens when the album is updated on the device.
// Query only happens one time.
if imageArray.isEmpty {
let photoOptions = PHFetchOptions()
photoOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]
let allPhotos = PHAsset.fetchAssets(with: photoOptions)
let fetchOptions = PHImageRequestOptions()
fetchOptions.deliveryMode = .highQualityFormat
fetchOptions.resizeMode = .exact
fetchOptions.normalizedCropRect = CGRect(x: 0, y: 0, width: view.frame.width, height: view.frame.width)
// Must be a synchronous call otherwise the view loads before images are retrieved (result is an empty view).
fetchOptions.isSynchronous = true
let imageSize : CGSize = CGSize(width: view.frame.width, height: view.frame.height)
//Retrieves Images using assets.
allPhotos.enumerateObjects { (assets, index, pointer) in
DispatchQueue.global(qos: .background).sync {
PHImageManager.default().requestImage(for: assets, targetSize: imageSize, contentMode: .aspectFit, options: fetchOptions) { (image, hashDictionary) in
guard let image = image else {return}
self.imageArray.append(image)
self.albumImages.append(ImageAlbum(images: image))
}
}
}
selectedImage.image = imageArray.first
selectedImage.contentMode = .scaleAspectFill
}
I would like to know how I can loop through an array and change a name after recognizer.state .ended. When I use forEach method it shows me only the last item from the array.
Array:
let persons = [
Name(name: "Theo", imageName: "theoPhoto"),
Name(name: "Matt", imageName: "mattPhoto"),
Name(name: "Tom", imageName: "tomPhoto")
]
UIPanGestureRecognizer:
#IBAction func handleCard(recognizer: UIPanGestureRecognizer) {
guard let card = recognizer.view else { return }
switch recognizer.state {
case .began:
initialCGRect = card.frame
case .changed:
handleChanged(recognizer, card)
case .ended:
handleEnded(recognizer, card)
default:
return
}
}
fileprivate func handleChanged(_ recognizer: UIPanGestureRecognizer, _ card: UIView) {
let panning = recognizer.translation(in: view)
let degrees : CGFloat = panning.x / 20
let angle = degrees * .pi / 180
let rotationTransformation = CGAffineTransform(rotationAngle: angle)
card.transform = rotationTransformation.translatedBy(x: panning.x, y: panning.y)
}
fileprivate func handleEnded(_ recognizer: UIPanGestureRecognizer, _ card: UIView) {
let transitionDirection: CGFloat = recognizer.translation(in: view).x > 0 ? 1 : -1
let shuldDismissCard = abs(recognizer.translation(in: view).x) > treshold
UIView.animate(withDuration: 1, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut, animations: {
if shuldDismissCard {
card.frame = CGRect(x: 600 * transitionDirection, y: 0, width: card.frame.width, height: card.frame.height)
} else {
card.transform = CGAffineTransform.identity
}
}) { (_) in
// Complete animation, bringing card back
card.transform = CGAffineTransform.identity
}
}
forEach method:
func setupCards() {
persons.forEach { (Person) in
cardView.image = UIImage(named: Person.imageName)
cardLabel.text = Person.name
}
}
If I put it on complete animation it's looping through an array and shows me the last item.
Your setupCards() method is wrong. It will always display the last item because you're updating only a single cardView. Also don't use the Person class name in the loop closure. You should hold a CardView array, and change the data accordingly.
func setupCards() {
var i = 0
persons.forEach { (p) in
let cardView = self.cards[i]
cardView.image = UIImage(named: p.imageName)
cardView.cardLabel.text = p.name
i += 1
}
}
I'm using third party library called BSImagePicker to select multiple images from the photo library. I can set maximum PHAssets to be selected, but the problem comes when I display the BSImagePickerController to choose more photos. Those PHAssets are just additionally added to the selectedAssets and practically duplicates the images. I tried to remove the duplicates PHAsset and images using but it's not working:
extension Array where Element: Equatable {
mutating func removeDuplicates() {
var result = [Element]()
for value in self {
if !result.contains(value) {
result.append(value)
}
}
self = result
}
}
func presentBSImagePickerController(vc: BSImagePickerViewController) {
self.bs_presentImagePickerController(vc, animated: true,
select: { (asset: PHAsset) -> Void in
}, deselect: { (asset: PHAsset) -> Void in
// User deselected an assets.
}, cancel: { (assets: [PHAsset]) -> Void in
// User cancelled. And this where the assets currently selected.
}, finish: { (assets: [PHAsset]) -> Void in
// User finished with these assets
for i in 0..<assets.count {
self.selectedAssets.append(assets[i])
}
self.selectedAssets.removeDuplicates()
self.convertAssetToImages()
}, completion: nil)
}
func convertAssetToImages() -> Void {
if selectedAssets.count != 0 {
for i in 0..<selectedAssets.count {
let manager = PHImageManager.default()
let option = PHImageRequestOptions()
var thumbnail = UIImage()
option.isSynchronous = true
manager.requestImage(for: selectedAssets[i], targetSize: CGSize(width: 200, height: 200), contentMode: .aspectFill, options: option, resultHandler: {(result, info)->Void in
thumbnail = result!
})
let data = UIImageJPEGRepresentation(thumbnail, 0.7)
let newImage = UIImage(data: data!)
self.photosArray.append(newImage! as UIImage)
self.photosArray.removeDuplicates()
print(self.photosArray.removeDuplicates())
DispatchQueue.main.async {
self.collectionView.reloadData() // reloads the collection view on main thread
}
}
}
}
The problem with
for i in 0..<assets.count {
self.selectedAssets.append(assets[i])
}
self.selectedAssets.removeDuplicates()
...is that your removeDuplicates (apart from being intolerably slow!) simply assumes that PHAsset equality is the mark of a duplicated image. It isn't. An asset is just a temporary representative of what's in the library. What you want to know is whether the local identifier of an asset matches that of one that you already have. I would suggest this:
var ids = Set(self.selectedAssets.map {$0.localIdentifier})
for asset in assets {
if !ids.contains(asset.localIdentifier) {
ids.insert(asset.localIdentifier)
self.selectedAssets.append(asset)
}
}
We make a list of all the local identifiers of the assets we already have. Then we append an asset (and its identifier) only if its identifier is not in the list. This will be much faster (because Set contains is much faster than Array contains), and should also prevent "the same photo" from appearing twice.
Step: 1. This code should be sufficient:
extension Array where Element: Equatable {
mutating func removeDuplicates() {
var result = [Element]()
for value in self {
if !result.contains(value) {
result.append(value)
}
}
self = result
}
}
Step: 2. "selectedAssets" which is PHAssets type array, you have to remove all the assets form "selectedAssets" array. Because you are showing multiImage from "photosArray" this array always has your image, and "selectedAssets" array always has reference value after being deleted. So remove all assets form "selectedAssets" array before for loop which is shown below. Or if you have any query, feel free to ask anything to me(Mohit Tomer)
// MARK:- Selecting Multiple Image From Gallery.
func showingImagePicker() {
let vc = BSImagePickerViewController()
var ids = Set(self.selectedAssets.map {$0.localIdentifier})
vc.maxNumberOfSelections = 5
vc.cancelButton.tintColor = UIColor.red
vc.selectionCharacter = "✓"
self.bs_presentImagePickerController(vc, animated: true, select: { (asset: PHAsset) -> Void in
print("Selected: \(asset)")
}, deselect: { (asset: PHAsset) -> Void in
print("Deselected: \(asset)")
}, cancel: { (assets: [PHAsset]) -> Void in
print("Cancel: \(assets)")
}, finish: { (assets: [PHAsset]) -> Void in
print("Finish: \(assets)")
self.selectedAssets.removeAll()
for asset in assets {
if !ids.contains(asset.localIdentifier) {
ids.insert(asset.localIdentifier)
self.selectedAssets.append(asset)
}
}
self.convertingAssetToImage()
}, completion: nil)
}
func convertingAssetToImage() -> Void {
var thumbnail = UIImage()
if selectedAssets.count != 0 {
for item in selectedAssets {
let manager = PHImageManager.default()
let option = PHImageRequestOptions()
option.isSynchronous = true
manager.requestImage(for: item, targetSize: CGSize(width: 200.0, height: 200.0), contentMode: .aspectFill, options: option, resultHandler: { (result, info) -> Void in
print("result", result as Any)
print("info", info as Any)
thumbnail = result!
})
self.photoArray.append(thumbnail)
self.photoDataArray.append(thumbnail.jpegData(compressionQuality: 0.5))
}
}
}
I currently have a custom UICollection which loads a users video library from their camera roll. Now I am currently able to add all the videos into an array; and it prints out the correct count of videos; however my UICollection is not displaying all of my videos in my library (which amounts to 119). Anyone have any clue why this would be occurring?
Here is my code:
struct Media {
var image:UIImage?
var videoURL:NSURL?
}
var mediaArray = [Media]()
func grabPhotos(){
let imgManager = PHImageManager.default()
let requestOptions = PHImageRequestOptions()
requestOptions.isSynchronous = true
requestOptions.deliveryMode = .highQualityFormat
let fetchOptions = PHFetchOptions()
fetchOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]
if let fetchResult : PHFetchResult = PHAsset.fetchAssets(with: .video, options: fetchOptions) {
if fetchResult.count > 0 {
for i in 0..<fetchResult.count{
var mediaItem = Media()
//Used for fetch Image//
imgManager.requestImage(for: fetchResult.object(at: i) as PHAsset , targetSize: CGSize(width: 400, height: 400), contentMode: .aspectFit, options: requestOptions, resultHandler: {
image, error in
let imageOfVideo = image! as UIImage
mediaItem.image = imageOfVideo;
//Used for fetch Video//
imgManager.requestAVAsset(forVideo: fetchResult.object(at: i) as PHAsset, options: PHVideoRequestOptions(), resultHandler: {(avAsset, audioMix, info) -> Void in
if let asset = avAsset as? AVURLAsset {
let videoData = NSURL(string: "\(asset.url)")
let duration : CMTime = asset.duration
let durationInSecond = CMTimeGetSeconds(duration)
print(durationInSecond)
mediaItem.videoURL = videoData!
self.mediaArray.append(mediaItem)
print(self.mediaArray.count)
}
})
})
}
}
else{
//showAllertToImportImage()//A function to show alert
}
}
}
And my cellForItemAt
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
{
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! VideoSelectionCVCell
cell.uploadedFile.image = mediaArray[indexPath.row].image
return cell
}
& Within my viewWillAppear I have the following creating the UICollection:
let flowLayout = UICollectionViewFlowLayout()
let collectionView = UICollectionView(frame: self.view.bounds, collectionViewLayout: flowLayout)
collectionView.register(VideoSelectionCVCell.self, forCellWithReuseIdentifier: cellId)
collectionView.delegate = self
collectionView.dataSource = self
collectionView.backgroundColor = UIColor.clear
self.view.addSubview(collectionView)
I think what is occurring is the screen is loading before the grabPhotos() occurs; and the grabPhotos() doesn't finish until the after the screen is loaded. I also have my UICollection being created in the viewWillAppear, so that would make it a private occurrence (if I'm correct). So I guess to fix this, I would need to make the UICollectionView public, but how would I do that if I am doing it programmatically + creating it in my View Will Appear?
There are a couple different ways you can solve this I think.
Move your remote image loading into cellforitemat
Add your collection view in Viewdidload, then at the end of the function call grabphotos.
In your grabphotos functions, call collectionView.reloadData() after you have the fetchResult.
Move imgManager.requestImage into cellforitem. This way you are only loading each image as the cells are rendered. The user isn't waiting for all the images to load before the collectionView is updated. You can add a prefetch if you are concerned about performance.
Use a DispatchGroup
If you really really want to load all the images before updating the collectionView, you can create a DispatchGroup to track the image downloads and then update the collectionView after it's all done.
struct Media {
var image:UIImage?
var videoURL:NSURL?
}
var mediaArray = [Media]()
let loadContentGroup = DispatchGroup()
func grabPhotos(){
loadContentGroup.notify(queue: DispatchQueue.main) { [weak self] in
guard let `self` = self else { return }
self.collectionView.reloadData()
}
let imgManager = PHImageManager.default()
let requestOptions = PHImageRequestOptions()
requestOptions.isSynchronous = true
requestOptions.deliveryMode = .highQualityFormat
let fetchOptions = PHFetchOptions()
fetchOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]
if let fetchResult : PHFetchResult = PHAsset.fetchAssets(with: .video, options: fetchOptions) {
if fetchResult.count > 0 {
for i in 0..<fetchResult.count{
var mediaItem = Media()
loadContentGroup.enter()
//Used for fetch Image//
imgManager.requestImage(for: fetchResult.object(at: i) as PHAsset , targetSize: CGSize(width: 400, height: 400), contentMode: .aspectFit, options: requestOptions, resultHandler: {
image, error in
let imageOfVideo = image! as UIImage
mediaItem.image = imageOfVideo;
//Used for fetch Video//
imgManager.requestAVAsset(forVideo: fetchResult.object(at: i) as PHAsset, options: PHVideoRequestOptions(), resultHandler: {(avAsset, audioMix, info) -> Void in
if let asset = avAsset as? AVURLAsset {
let videoData = NSURL(string: "\(asset.url)")
let duration : CMTime = asset.duration
let durationInSecond = CMTimeGetSeconds(duration)
print(durationInSecond)
mediaItem.videoURL = videoData!
self.mediaArray.append(mediaItem)
print(self.mediaArray.count)
self.loadContentGroup.leave()
}
})
})
}
}
else{
//showAllertToImportImage()//A function to show alert
}
}
}
Here is my code to do that:
import UIKit
import Photos //Photos.h del framework Photos
import MediaPlayer
import AssetsLibrary
let reuseIdentifier = "PhotoCell"
let albumName = "My App"
class PhotosViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
var assetCollection:PHAssetCollection!
var photoAsset:PHFetchResult!
var imageManager = PHImageManager.defaultManager()
var albumFound:Bool = false
#IBOutlet weak var collectionView: UICollectionView!
#IBAction func creaFilmato(sender: AnyObject) {
println("Crea il tuo filmato")
/* With a static array works perfectly
let img1:UIImage = UIImage(named: "image1.jpg")!
let img2:UIImage = UIImage(named: "image2.jpg")!
let img3:UIImage = UIImage(named: "image3.jpg")!
var array:Array<UIImage> = [img1,img2,img3]*/
let img1:UIImage = UIImage(named: "image1.jpg")!
let settings:NSDictionary = CEMovieMaker.videoSettingsWithCodec(AVVideoCodecH264, withWidth: img1.size.width, andHeight: img1.size.height)
let prova = CEMovieMaker(settings: settings)
prova.createMovieFromImages([self.photoAsset.copy()], withCompletion: {(success:Bool, fileURL:NSURL!) in
if(success){
self.viewMovieAtURL(fileURL)
/*// Salvo il filmato in cameraRoll
let library:ALAssetsLibrary = ALAssetsLibrary()
library.writeVideoAtPathToSavedPhotosAlbum(fileURL, completionBlock: {(assetURL:NSURL!, error) in
if((error) != nil) {
println("Errore nel salvataggio del filmato %#",error)
}
})*/
}
})
println("Esportato!")
}
func viewMovieAtURL(fileURL:NSURL!)->Void {
let playerController:MPMoviePlayerViewController = MPMoviePlayerViewController(contentURL: fileURL!)
playerController.view.frame = self.view.bounds
self.presentMoviePlayerViewControllerAnimated(playerController)
playerController.moviePlayer.prepareToPlay()
playerController.moviePlayer.play()
self.view.addSubview(playerController.view)
}
In my opinion the wrong code is [self.photoAsset.copy()] because photoAsset return PHFetchResult and not an Array of UIImage. What is the code to obtain an array of images starting from a PHFetchResult in Swift?
var images = [UIImage]()
let targetSize: CGSize = // your target size
let contentMode: PHImageContentMode = // your content mode
// photoAsset is an object of type PHFetchResult
photoAsset.enumerateObjectsUsingBlock {
object, index, stop in
let options = PHImageRequestOptions()
options.synchronous = true
options.deliveryMode = .HighQualityFormat
PHImageManager.defaultManager().requestImageForAsset(object as PHAsset, targetSize: targetSize, contentMode: contentMode, options: options) {
image, info in
images.append(image)
}
}
This is how I do it with Swift 4.2
let fetchOptions = PHFetchOptions()
let allPhotos = PHAsset.fetchAssets(with: .image, options: fetchOptions)
let targetSize = CGSize(width: 100, height: 100) // your target size
allPhotos.enumerateObjects {
object, index, stop in
var thumbnail = UIImage()
let options = PHImageRequestOptions()
options.isSynchronous = true
options.deliveryMode = .highQualityFormat
PHImageManager.default().requestImage(for: object, targetSize: targetSize, contentMode: .aspectFill, options: options, resultHandler: { image, _ in
thumbnail = image!
})
}
Hope this help!