i currently developping an app which retrieve url from JSON, and create some ImageView with the URLS.
When i decode my JSON, i stock every URL into an array.
The problem is, how can i create an ImageView for each URLS which are in my array ?
This is my code :
if(success == 1)
{
DataFromJSon = jsonData["objects"] as! NSArray
repos.removeAll()
for one in DataFromJSon {
let repo = Repository(jsonData: one as! [String : AnyObject])
repos.append(repo)
}
for repo in repos {
lines.insert(repo.picture_url, atIndex: i)
dump(lines)
i++
}
//Construct the imgUrl to get an image URL for the pages
if let urlString: NSString = MyArray {
if let url = NSURL(string: urlString as String) {
if let data = NSData(contentsOfURL: url) {
newCard.image = UIImage(data: data)!
newCard.content = titleApp
newCard.desc = prixApp
self.data.append(newCard)
NSLog("fetch new data")
}
}
}
}
Thank you !
Watch out :
Here is a minimum working example
var urls = [NSURL]()
urls.append(NSURL(string: "https://yourimage1")!)
urls.append(NSURL(string: "https://yourimage2")!)
for url in urls {
dispatch_async(dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0)) {
if let data = NSData(contentsOfURL: url) {
let image = UIImage(data: data)!
let view = UIImageView(image: image)
dispatch_async(dispatch_get_main_queue()) {
// Display your view here
}
}
}
}
As referred to by #Abizern, you should put the downloading of the data in a background queue, and use the main queue for updating the UI. You can scale the image by setting the size of the UIImageView and then using .ScaleAspectFit or .ScaleAspectFill on the contentMode property according to your needs.
Edit
I just tested it with the imagelinks you provided, and the code works fine.
var urls = [NSURL]()
urls.append(NSURL(string: "http://www.parkers.co.uk/Images/PageFiles/74655/firstdrive9.jpg")!)
urls.append(NSURL(string: "http://www.rsiauto.fr/images/BMW/330-Ci/330-Ci-1.jpg")!)
var i = 0
let width = self.view.frame.width/2
let height = self.view.frame.height
for url in urls {
dispatch_async(dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0)) {
if let data = NSData(contentsOfURL: url) {
let image = UIImage(data: data)!
let frame = CGRectMake(CGFloat(i++)*width, 0, width, height)
i++
let view = UIImageView(image: image)
view.frame = frame
view.contentMode = .ScaleAspectFit
view.layer.masksToBounds = true
view.backgroundColor = UIColor.redColor()
dispatch_async(dispatch_get_main_queue()) {
self.view.addSubview(view)
print("Done")
}
}
}
}
Here a screenshot
Related
I have an app where a user reviews an array of users. Example:
["user1","user2","user3","user4"]
When the user reviews the first user the app should present the second user for voting. Unfortunately when a user votes on the user2, user3, user4; the value for user1 is what's put in the database. I tested the PHP via postman and there's no issue there, so it has to be in the swift code. Here is my code:
func loadCards()->[String] {
let username = user!["username"] as! String
let url = URL(string: "http://localhost/shotsCenter.php")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
let body = "username=\(username)"
request.httpBody = body.data(using: .utf8)
// launch session
URLSession.shared.dataTask(with: request) { data, response, error in
// getting main queue of proceeding inf to communicate back, in another way it will do it in background
// and user will no see changes :)
DispatchQueue.main.async(execute: {
if error == nil {
do {
let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? [String: AnyObject]
// clean up
self.valueArray.removeAll(keepingCapacity: false)
self.circleArray.removeAll(keepingCapacity: false)
// delcare new secure var to store json
guard let parseJSON = json else {
print("Error while parsing")
return
}
// declare new secure var to store $returnArray["users"]
guard let parseUSERS = parseJSON["users"] else {
print(parseJSON["message"])
return
}
self.valueArray = parseUSERS as! [AnyObject]
if self.valueArray.count > 0 {
let num_currentLoadedCardsArrayCap = (self.valueArray.count > MAX_BUFFER_SIZE) ? MAX_BUFFER_SIZE : self.valueArray.count
for (i, value) in self.valueArray.enumerated() {
let ava = self.valueArray[i]["ava"]
let id = self.valueArray[i]["id"]
let age = (NSString(format: "%#", self.valueArray[i]["age"] as! CVarArg))
let city = self.valueArray[i]["city"]
let state = self.valueArray[i]["state"]
self.age.append(age as AnyObject)
self.city.append(city as AnyObject)
self.state.append(state as AnyObject)
let url = NSURL(string: ava! as! String)!
let imageData = try? Data(contentsOf: url as URL)
let image = UIImage(data: imageData!)!
self.circleArray.append(image)
let reviewed = self.valueArray[i]["username"]
self.reviewed.append((reviewed as AnyObject) as! String)
print("reviewed user", reviewed! as Any)
let newCard = self.createDraggableViewWithData(at: i, value:value as! NSDictionary)
self.allCardsArray.append(newCard)
if i < num_currentLoadedCardsArrayCap {
self.currentLoadedCardsArray.append(newCard)
}
}
for (i,_) in self.currentLoadedCardsArray.enumerated() {
if i > 0 {
self.viewTinderBackGround.insertSubview(self.currentLoadedCardsArray[i], belowSubview: self.currentLoadedCardsArray[i - 1])
}
else {
self.viewTinderBackGround.addSubview(self.currentLoadedCardsArray[i])
}
self.currentIndex += 1
}
self.animateCardAfterSwiping()
self.perform(#selector(self.createDummyCard), with: nil, afterDelay: 1.0)
}
} catch {
// get main queue to communicate back to user
DispatchQueue.main.async(execute: {
let message = "\(error)"
})
return
}
} else {
DispatchQueue.main.async(execute: {
let message = error!.localizedDescription
})
return
}
})
} .resume()
return reviewed
}
func insertShot(_ rating: String, _ reviewed2: NSDictionary) {
let reviewer = user!["username"] as! String
let reviewed2 = reviewed[index]
let url = URL(string: "http://localhost/shotsCenter.php")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
// param to be passed to php file
let param = [
"user" : reviewer,
"revieweduser" : reviewed2,
"rating" : rating
] as [String : Any]
// body
let boundary = "Boundary-\(UUID().uuidString)"
request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
// ... body
request.httpBody = createBodyWithParams(param as? [String : String], boundary: boundary)
// launch session
URLSession.shared.dataTask(with: request) { data, response, error in
// get main queu to communicate back to user
DispatchQueue.main.async(execute: {
if error == nil {
do {
// json containes $returnArray from php
let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? NSDictionary
// declare new var to store json inf
guard let parseJSON = json else {
print("Error while parsing")
return
}
// get message from $returnArray["message"]
let message = parseJSON["message"]
//print(message)
// if there is some message - post is made
if message != nil {
// reset UI
// self.msgTxt.text = ""
// switch to another scene
//self.tabBarController?.selectedIndex = 3
//_ = self.navigationController?.popViewController(animated: true)
}
} catch {
// get main queue to communicate back to user
DispatchQueue.main.async(execute: {
let message = "\(error)"
appDelegate.infoView(message: message, color: colorSmoothRed)
})
return
}
} else {
// get main queue to communicate back to user
DispatchQueue.main.async(execute: {
let message = error!.localizedDescription
appDelegate.infoView(message: message, color: colorSmoothRed)
})
return
}
})
}.resume()
}
No idea what currentIndex is for. Sth about the view hierarchy?
index is an interesting candidate. The only times it is used is in
let reviewed2 = reviewed[index]
and I see no modification to it. Since usernames are stored in there with
let reviewed = self.valueArray[i]["username"]
I think you are sending the same username for all four ratings, since reviewed2 is then made of the .httpBody. That's a guess from what I can see.
Tip: Codable
Codable may save you a lot of the ugliness of juggling around with AnyObject & co, since you'll have a [User] array instead of.. 4 separate arrays which you access with [index]. https://app.quicktype.io will get you started quickly by providing you with the parsing code for the given JSON.
I've got Thread 1: Fatal error: Index out of range looping my 7 photos from array in NSViews.
How to fix it?
let url = URL(fileURLWithPath: NSHomeDirectory()).appendingPathComponent("Desktop/ArrayOfElements")
do {
let fileURLs = try FileManager.default.contentsOfDirectory(at: url, includingPropertiesForKeys: nil, options: [.skipsHiddenFiles]).reversed()
let photos = fileURLs.filter { $0.pathExtension == "jpg" }
for index in photos {
let image = [NSImage(data: try Data(contentsOf: index))]
for view in arrayOfViews {
let i = Int(arc4random_uniform(UInt32(photos.count-1)))
view.image = image[i]
}
}
} catch {
print(error)
}
It seems that this line is wrong:
view.image = image[i]
image array has length = 1
Use view.image = image[0] instead
EDIT
let url = URL(fileURLWithPath: NSHomeDirectory()).appendingPathComponent("Desktop/ArrayOfElements")
do {
let fileURLs = try FileManager.default.contentsOfDirectory(at: url, includingPropertiesForKeys: nil, options: [.skipsHiddenFiles]).reversed()
let photos = fileURLs.filter { $0.pathExtension == "jpg" }
for view in arrayOfViews {
let i = Int(arc4random_uniform(UInt32(photos.count-1)))
let image = NSImage(data: try Data(contentsOf: photos[i]))
view.image = image
}
} catch {
print(error)
}
Try to create a category to Collection
extension Collection where Index == Int {
/**
Gives a random element of the collection.
- returns: A random element of the collection.
*/
func randomElement() -> Iterator.Element? {
return isEmpty ? nil : self[Int(arc4random_uniform(UInt32(endIndex)))]
}
}
Usage
let numbers = [1,2,3,4,5,6,7,8,9,10]
let randomNumber = numbers.randomElement()
print(randomNumber!)
Edit:
I guess you are making mistake in the following code
for view in arrayOfViews {
let i = Int(arc4random_uniform(UInt32(photos.count-1)))
view.image = image[i]
}
Let's assume photos objects having 10 elements and image object have less than 10 elements, so in such case, you will get this type of error. So change it to the following
for view in arrayOfViews {
let i = Int(arc4random_uniform(UInt32(image.endIndex)))
view.image = image[i]
}
I am new in swift. Who can help? I have array with 3 links how can i take one link from this array and show in UIImageView then second link and then third? I make with one link but not understand how i can make with array. Thnx.
class ViewController: UIViewController {
#IBOutlet weak var imgView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
var image = UIImage()
var imageData: Data?
let links = ["http://s.ill.in.ua/i/gallery/950x0/2694/146192.jpg","http://s.ill.in.ua/i/gallery/950x0/2694/146190.jpg","http://s.ill.in.ua/i/gallery/950x0/2694/146202.jpg"]
let url = URL(string: links[0])
do {
imageData = try Data(contentsOf: url!)
}
catch {
print("error")
}
if let value = imageData {
image = UIImage(data:value)!
imgView.image = image
}
}
}
You can easily iterate through array like this:
for link in links {
// This will iterate through array, so you can do something specific on each step
let url = URL(string: link)
do {
imageData = try Data(contentsOf: url!)
}
catch {
print("error")
}
if let value = imageData {
image = UIImage(data:value)!
imgView.image = image
print("Updated imageView with \(link)!")
}
}
Just use this way :
let links = ["http://s.ill.in.ua/i/gallery/950x0/2694/146192.jpg","http://s.ill.in.ua/i/gallery/950x0/2694/146190.jpg","http://s.ill.in.ua/i/gallery/950x0/2694/146202.jpg"]
Take three different ImageView ( for a while ) or u can use tableview to show image and use following link : https://stackoverflow.com/a/37019507/3400991
for linkValue in links {
yourimageview.imageFromServerURL(urlString: linkValue as! String)
}
this is one of the way u can show image into your imageview.
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
}
}
}
I am trying to create a file name from the URL. I have to forcefully remove the first item in the array every time. Like the C# implementation where they check for empty string.
Is it possible to remove the "/" in the array? Is there a better way to implement this?
let url = "https://stackoverflow.com/questions/ask"
var filePathComponents:[String] = []
filePathComponents = assetURL.pathComponents as! [String]
filePathComponents.removeAtIndex(0)
let fileName = "-".join(filePathComponents)
I would recommend
filePathComponents.filter { return $0 != "/" }
You don't have to use mutability if you use range indexing (with ..<).
And to make it convenient to use, let's put it in an extension as a computed property.
Example for String:
extension String {
var pathNameWithoutPrefix: String {
get {
return "-".join(self.pathComponents[2 ..< self.pathComponents.count])
}
}
}
let filePath = "http://stackoverflow.com/questions/ask".pathNameWithoutPrefix
println(filePath) // "questions-ask"
Example for NSURL:
extension NSURL {
var pathURLWithoutPrefix: NSURL? {
get {
if let filePathComponents = self.pathComponents as? [String] {
return NSURL(string: "-".join(filePathComponents[1 ..< filePathComponents.count]))
}
return nil
}
}
}
if let url = NSURL(string: "http://stackoverflow.com/questions/ask"),
let fileURL = url.pathURLWithoutPrefix {
println(fileURL) // "questions-ask"
}
Here's the same extensions for Swift 2:
extension String {
var pathNameWithoutPrefix: String {
get {
let str = self as NSString
return "-".join(str.pathComponents[2 ..< str.pathComponents.count])
}
}
}
extension NSURL {
var pathURLWithoutPrefix: NSURL? {
get {
if let filePathComponents = self.pathComponents {
return NSURL(string: "-".join(filePathComponents[1 ..< filePathComponents.count]))
}
return nil
}
}
}
let linkURL = NSURL(string: "http://stackoverflow.com/questions/ask")!
if let comps = linkURL.pathComponents as? [String] {
let fileName = "-".join(dropFirst(comps)) // "questions-ask"
}
Swift 2.0
let linkURL = NSURL(string: "http://stackoverflow.com/questions/ask")!
if let comps = linkURL.pathComponents?.dropFirst() {
let fileName = "-".join(comps) // "questions-ask"
}
Different approach using the host and path properties of NSURL
if let assetURL = NSURL(string: "http://stackoverflow.com/questions/ask") {
let fileNameWithHost = assetURL.host!.stringByAppendingPathComponent(assetURL.path!) // "stackoverflow.com/questions/ask"
let fileNameWithoutHost = assetURL.path!.substringFromIndex(assetURL.path!.startIndex.successor()) // "questions/ask"
}