I recently switched from Objective C to Swift, so I am not familiar with finding mistakes in my own algorithm. I would like to understand what am I missing here.
I've got ViewController with ScrollView added. Everything compiles and runs, no SIGARBT's or so, but It won't display the desired images. Every IBOutlet is connected propely in its place.
Below is the function for all of the UI Elements:
func UIUpdate() {
guard let imageContent = content as? ImageContent,
pages = imageContent.pages?.array as? [Image] else {
return
}
self.pages = pages
self.mainTitleLabel.text = imageContent.title
self.pageControl.numberOfPages = pages.count
let imageWidth = UIScreen.mainScreen().bounds.size.width// - 20
self.scrollView.frame.size.width = imageWidth
self.scrollView.frame.size.height = imageWidth / 1.883
self.scrollView.contentSize = CGSizeMake(imageWidth *
CGFloat(pages.count), self.scrollView.frame.size.height)
for (index, page) in pages.enumerate() {
autoreleasepool {
guard let imageData = page.image, let image = UIImage(data: imageData) else { return }
let imageView = UIImageView(image: image)
imageView.contentMode = .ScaleAspectFit
imageView.frame.size = CGSizeMake(self.scrollView.frame.size.width, self.scrollView.frame.size.height)
self.scrollView.addSubview(imageView)
self.addConstraints(toImageView: imageView, atIndex: index, lastIndex: pages.count - 1)
}
}
}
Image class is where images and titles are parsed. So this class is perfectly fine, because I have been using it in another project. This method is the only one called for getting those images. There are methods though, but I think they've got nothing to do with my problem.
Could You guys show me the mistake! Any help would be appreciated!
Related
Swift 3 / Xcode 8.3.3
When I click on the screen, an image appears (code 1) at the place of the click (if I click 5 times, 5 images appear) and each image has a tag. Now I would like to delete the images one by one at each click on a button (code 2) but only the last image is deleted...
I did a lot of research but none of the results worked for me.
code 1:
var imageView : UIImageView!
var lstTagImage: [Int] = []
var concatenateInt: String = ""
var lstPoint: [Point] = []
concatenateInt = "\(Int(i))\(Int(j))"
lstPoint.append(Point(x: i, y: j, weigth: weigthChip))
imageView = UIImageView(frame:CGRect(x: X,
y: Y,
width: 20, height: 20));
imageView.tag = Int(concatenateInt)!
lstTagImage.append(Int(concatenateInt)!)
imageView.image = UIImage(named: imageString)
self.view.addSubview(imageView)
code 2:
#IBAction func undoButton(_ sender: UIButton) {
if (lstPoint.count != 0){
lstPoint.remove(at: lstPoint.count - 1)
imageView.removeFromSuperview()
} else {
print("list empty")
}
}
An alternate, more elegant approach would be something like this:
let addedImages = [UIImageView]()
// When adding an image to the view
addedImages.append(imageView)
self.view.addSubview(imageView)
Then it's easy to delete.
// When deleting image from view
if let imageView = addedImages.last {
imageView.removeFromSuperview()
addedImages.removeLast()
}
You should use viewWithTag to retreive the last tag, remove that tag from the array and remove the image from its superview.
if lstPoint.count != 0,
let lastTag = lstTagImage.last {
lstPoint.remove(at: lstPoint.count - 1)
lstTagImage.removeLast()
let imageView = view.viewWithTag(lastTag)
imageView.removeFromSuperview()
}
I currently have a double for-loop that creates an X by Y grid of UIView CGRect squares. The loop also adds each UIView/Square of the grid to a 2D array allowing me to access each Cell of the grid and alter color/positioning by the index values of the loop.
The loop seems to work fine and displays the Cells/Squares perfectly, however after a while I want to remove all of the squares and also empty the array (but not entirely delete) to make room for a new next grid (which may be of a different size). I created a function to remove all the views from the superview.
This is how I am creating each "Cell" of the grid and placing each into the 2D array:
let xStart = Int(size.width/2/2) - ((gridSizeX * gridScale)/2)
let yStart = Int(size.height/2/2) - ((gridSizeY * gridScale)/2)
let cell : UIView!
cell = UIView(frame: CGRect(x: xStart + (xPos * gridScale), y:yStart + (yPos * gridScale), width:gridScale, height:gridScale))
cell.layer.borderWidth = 1
cell.layer.borderColor = UIColor(red:0.00, green:0.00, blue:0.00, alpha:0.02).cgColor
cell.backgroundColor = UIColor(red:1.00, green:1.00, blue:1.00, alpha:0)
cell.tag = 100
self.view?.addSubview(cell)
gridArray[xPos][yPos] = cell
The 2D array is being created on application load like so:
gridArray = Array(repeating: Array(repeating: nil, count: gridSizeY), count: gridSizeX)
I have tried to search for a solution to remove the UIViews from the superview however all answers from other Questions don't seem to work for me. I have tried to add cell.tag = 100 to each UIView and then remove all UIViews with the Tag Value of 100 like:
for subview in (self.view?.subviews)! {
if (subview.tag == 100) {
subview.removeFromSuperview()
}
}
However no luck. I have also tried to use this method:
self.view?.subviews.forEach({
$0.removeConstraints($0.constraints)
$0.removeFromSuperview()
})
The main differences I notice in my code compared to other peoples answers is that I have "?" and "!" at places in the code. I researched about what they mean and understood most of it however I still do not know how to fix my code because if it, and feel as though that is the issue. All I know is that the attempts to remove the UIViews from the superview does not work, and without the "?" and "!"s the code doesn't run at all.
How about to create tag for each cell you are using for example:
//Init value for for the tag.
var n = 0
func prepareCell() -> UIView {
let xStart = Int(size.width/2/2) - ((gridSizeX * gridScale)/2)
let yStart = Int(size.height/2/2) - ((gridSizeY * gridScale)/2)
let cell : UIView!
cell = UIView(frame: CGRect(x: xStart + (xPos * gridScale), y:yStart + (yPos * gridScale), width:gridScale, height:gridScale))
cell.layer.borderWidth = 1
cell.layer.borderColor = UIColor(red:0.00, green:0.00, blue:0.00, alpha:0.02).cgColor
cell.backgroundColor = UIColor(red:1.00, green:1.00, blue:1.00, alpha:0)
cell.tag = n
//Each cell should have new value
n += 1
return cell
}
And now remove required views.
func removeViews() {
for z in 0...n {
if let viewWithTag = self.view.viewWithTag(z) {
viewWithTag.removeFromSuperview()
}
else {
print("tag not found")
}
}
}
Example that is working in the playground:
var n = 0
let mainView = UIView()
func createView() -> UIView {
let view = UIView()
view.tag = n
n += 1
return view
}
for i in 0...16 {
mainView.addSubview(createView())
}
func removeViews() {
for z in 0...n {
if let viewWithTag = mainView.viewWithTag(z) {
viewWithTag.removeFromSuperview()
print("removed")
}
else {
print("tag not found")
}
}
}
You might be overlooking a much, much simpler way to do this...
You have built a 2D Array containing references to the "cells" as you populated your view. So, just use that Array to remove them.
// when you're ready to remove them
for subArray in gridArray {
for cell in subArray {
cell.removeFromSuperview()
}
}
// clear out the Array
gridArray = Array<Array<UIView>>()
I want to copy one UIView to another view without making it archive or unarchive.
Please help me if you have any solution.
I tried with by making an extension of UIView as already available an answer on Stack over flow. But its crashing when I pass the view with pattern Image Background color.
The code related to my comment below:
extension UIView
{
func copyView() -> UIView?
{
return NSKeyedUnarchiver.unarchiveObjectWithData(NSKeyedArchiver.archivedDataWithRootObject(self)) as? UIView
}
}
I've just tried this simple code in a Playground to check that the copy view works and it's not pointing the same view:
let originalView = UIView(frame: CGRectMake(0, 0, 100, 50));
originalView.backgroundColor = UIColor.redColor();
let originalLabel = UILabel(frame: originalView.frame);
originalLabel.text = "Hi";
originalLabel.backgroundColor = UIColor.whiteColor();
originalView.addSubview(originalLabel);
let copyView = originalView.copyView();
let copyLabel = copyView?.subviews[0] as! UILabel;
originalView.backgroundColor = UIColor.blackColor();
originalLabel.text = "Hola";
originalView.backgroundColor; // Returns black
originalLabel.text; // Returns "Hola"
copyView!.backgroundColor; // Returns red
copyLabel.text; // Returns "Hi"
If the extension wouldn't work, both copyView and originalView would have same backgroundColor and the same would happen to the text of the labels. So maybe there is the possibility that the problem is in other part.
Original Post
func copyView(viewforCopy: UIView) -> UIView {
viewforCopy.hidden = false //The copy not works if is hidden, just prevention
let viewCopy = viewforCopy.snapshotViewAfterScreenUpdates(true)
viewforCopy.hidden = true
return viewCopy
}
Updated for Swift 4
func copyView(viewforCopy: UIView) -> UIView {
viewforCopy.isHidden = false //The copy not works if is hidden, just prevention
let viewCopy = viewforCopy.snapshotView(afterScreenUpdates: true)
viewforCopy.isHidden = true
return viewCopy!
}
So Ive got a Feed displaying posts made by users (Using Parse). At present the cell contains a title, profile image of user who posted, the date, content(text), an imageView (for an image they post) and a like & comment button.
My question is, Using autolayout what is the best approach to displaying the image they posted. Currently I have the imageView height set to 0 at all times, I then reference the constant height of the imageView in the code.
The app pulls down all the posts and starts loading the tableView of posts.
The code for loading an image in cell is
if let imageToFetch = post.objectForKey("postImage") as? PFObject {
self.postImageHeight.constant = 500
if let fetchImage = imageToFetch.objectForKey("image") as? PFFile{
fetchImage.getDataInBackgroundWithBlock {
(imageData: NSData?, error: NSError?) -> Void in
if error == nil {
if let imageData = imageData {
let image = UIImage(data:imageData)
self.postImage.image = image
self.imageGesture(self.postImage)
}
}
}
} else {
//Make the size constarints of the image 0
self.postImageHeight.constant = 0
}
But it seems like a unstable approach as I'm hardcoding the image height. Any help on the issue would be appriciated.
You can change them using the image's size property. But for this, you need to use a fixed width referenced here as kImageWidth
self.postImageHeight.constant = kImageWidth * image.size.height / image.size.width
self.layoutIfNeeded()
Simple answer: calculate postImageHeight manually after setting postImage.image:
/// .....
self.postImage.image = image
self.postImageHeight.constant = self.postImage.bounds.size.width * image.size.height / image.size.width
/// .....
A better option is to add a special method to UITableViewCell subclass:
func setPostImageWithImage(image: UIImage?) {
postImageView.image = image
// Manually adjust image height.
if let image = image {
postImageViewHeightConstraint.constant = postImageView.bounds.size.width * image.size.height / image.size.width
}
else {
// Collapse if there is no image.
postImageViewHeightConstraint.constant = 0
}
}
I got a problem with UIImage. Each time I call the function below, the memory increases by 5 MB.
I have 200 images, I'm scared it will crash on my iPad 2 (512 MB) so I did added pagination to my app - each page contains 20 images.
And it is still crashing. How can I reduce the amount memory on each page?
func loadImage(index:Int){
if self.imgPaths.count == 0 {
println("Has not data")
actInd.stopAnimating()
return
}
var imgURL: NSURL = NSURL(string: self.imgPaths[index].stringByTrimmingCharactersInSet(.whitespaceAndNewlineCharacterSet()))!
let width:CGFloat = self.view.bounds.width
let height:CGFloat = self.view.bounds.height
var view:UIView = UIView(frame: CGRectMake(0, 0, width, height));
if let imgObj = self.dicData[index] {
}
else
{
println("imgURL \(imgURL)")
let request: NSURLRequest = NSURLRequest(URL: imgURL)
NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue(), completionHandler: {(response: NSURLResponse!,data: NSData!,error: NSError!) -> Void in
if error == nil {
let imgItem = UIImage(data: data)!
var te :Float = self.imgPaths.count > 0 ? Float(index + 1) / Float(self.imgPaths.count) : 1
self.progressView.setProgress(te, animated: true)
if let imgObj = self.dicData[index] {
if index < self.imgPaths.count - 1
{
var nextIndex:Int = index + 1
self.loadImage(nextIndex)
}
if(index == self.imgPaths.count - 1)
{
if self.currentImageIndex > 0
{
self.isAddedFirstImg = true
}
if !self.isAddedFirstImg
{
self.scrollViews[0].zoomScale = self.zoomScales[0]
self.view.insertSubview(self.scrollViews[0], belowSubview: self.tabBar.viewWithTag(77)!)
self.isAddedFirstImg = true
}
self.actInd.stopAnimating()
println("loaded image")
}
}
else
{
self.dicData[index] = UIImageView(image: imgItem)
self.dicData[index]?.frame = CGRect(origin: CGPointMake(0.0, 0.0), size:imgItem.size)
// 2
self.scrollViews[index].addSubview(self.dicData[index]!)
self.scrollViews[index].contentSize = imgItem.size
// 3
var doubleTapRecognizer = UITapGestureRecognizer(target: self, action: "scrollViewDoubleTapped:")
doubleTapRecognizer.numberOfTapsRequired = 2
doubleTapRecognizer.numberOfTouchesRequired = 1
self.scrollViews[index].addGestureRecognizer(doubleTapRecognizer)
var singleTapRecognizer = UITapGestureRecognizer(target: self, action: "scrollViewSingleTapped:")
singleTapRecognizer.numberOfTapsRequired = 1
singleTapRecognizer.numberOfTouchesRequired = 1
self.scrollViews[index].addGestureRecognizer(singleTapRecognizer)
var swipeRight = UISwipeGestureRecognizer(target: self, action: "respondToSwipeGesture:")
swipeRight.direction = UISwipeGestureRecognizerDirection.Right
self.scrollViews[index].addGestureRecognizer(swipeRight)
var swipeLeft = UISwipeGestureRecognizer(target: self, action: "respondToSwipeGesture:")
swipeLeft.direction = UISwipeGestureRecognizerDirection.Left
self.scrollViews[index].addGestureRecognizer(swipeLeft)
// 4
var scrollViewFrame = self.scrollViews[index].frame
var scaleWidth = scrollViewFrame.size.width / self.scrollViews[index].contentSize.width
var scaleHeight = scrollViewFrame.size.height / self.scrollViews[index].contentSize.height
var minScale = min(scaleWidth, scaleHeight)
self.zoomScales[index] = minScale
self.scrollViews[index].minimumZoomScale = minScale
// 5
self.scrollViews[index].maximumZoomScale = 1.0
self.scrollViews[index].delegate = self
// 6
self.centerScrollViewContents(index)
dispatch_async(dispatch_get_main_queue(), {
println("downloaded image index: \(index) CH.\(self.chapterID)")
if(index == 0)
{
self.scrollViews[0].zoomScale = self.zoomScales[0]
self.view.insertSubview(self.scrollViews[0], belowSubview: self.tabBar.viewWithTag(77)!)
self.actInd.stopAnimating()
}
if index < self.imgPaths.count - 1 && !self.stopDownload
{
var nextIndex:Int = index + 1
self.loadImage(nextIndex)
}
if(index == self.imgPaths.count - 1)
{
if self.currentImageIndex > 0
{
self.isAddedFirstImg = true
}
if !self.isAddedFirstImg
{
self.scrollViews[0].zoomScale = self.zoomScales[0]
self.view.insertSubview(self.scrollViews[0], belowSubview: self.tabBar.viewWithTag(77)!)
self.isAddedFirstImg = true
}
self.actInd.stopAnimating()
println("loaded image")
}
})
}
}
else {
println("Error: \(error.localizedDescription)")
}
})
}
}
Your intuition here (not trying to load 200 high resolution images at the same time) is right.
There are two issues here:
If the image dimensions greatly exceed the dimensions of the image views in which you're showing them (or more accurately, the dimensions of the image view times the device's scale), then you should resize the image to the size appropriate for the current image view. Loading a large image into a small image view still requires a great deal of memory. So, resize the image if necessary.
You should only hold in memory those images that are visible at any given time. Those that scroll out of view should be removed from memory. The images not currently visible should be cached to some persistent storage so that you can free up memory while being able to re-retrieve the image quickly without having to go back to the web service for the image.
It would appear that you're manually adding image view objects to a scroll view. To do this properly with a scroll view involves complicated UIScrollViewDelegate logic that determines which image views are visible and removes the ones that outside the visible portion of the scroll view. It's much easier to use UITableView or UICollectionView or UIPageViewController, which give you mechanisms to manager/reuse image views as they scroll in and out of view. Deciding which of these is appropriate depends a little upon the desired UI. But hopefully this illustrates the idea: Don't try to load all images in the scroll view at the same time. And rather than manually paging yourself, consider using one of the iOS built-in controls which greatly simplifies this process.
As an aside, you might also consider using a image framework like the UIImageView category provided by SDWebImage or AFNetworking. These allow a very simple mechanism for asynchronously retrieving images from web service. You'll still want to use one of the above mechanisms for managing the image views, but either of these two frameworks will simplify your networking code (lazy loading, asynchronous image retrieval, caching of images, concurrent image retrieval which will greatly improve performances, etc.).
You need to use Haneke instead NSdata. The problem is your app download synchronously, not asynchronously.
Haneke
imageView.hnk_setImageFromURL(url)
Reference:
http://blog.revivalx.com/2015/02/23/uitableview-tutorial-in-swift-using-alamofire-haneke-and-swiftyjson/
https://github.com/Haneke/HanekeSwift