Whenever I tap on the last row in the column, the app crashes on the iPhone, but not on the simulator. Everything else works fine. I do not get any error message. The code is working perfectly fine along with the simulator.
import UIKit
class EarthVC: UIViewController {
#IBOutlet var slideShow1: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
slideShow1.animationImages = [
UIImage(named: "Earth1.jpg")!,
UIImage(named: "Earth2.jpg")!,
UIImage(named: "Earth3.jpg")!,
UIImage(named: "Earth4.jpg")!,
UIImage(named: "Earth5.jpg")!,
UIImage(named: "Earth6.jpg")!,
UIImage(named: "Earth7.jpg")!,
UIImage(named: "Earth8.jpg")!,
UIImage(named: "Earth9.jpg")!,
UIImage(named: "Earth10.jpg")!,
UIImage(named: "Earth11.jpg")!,
UIImage(named: "Earth12.jpg")!,
UIImage(named: "Earth13.jpg")!,
UIImage(named: "Earth14.jpg")!,
UIImage(named: "Earth15.jpg")!,
UIImage(named: "Earth16.jpg")!,
UIImage(named: "Earth17.jpg")!,
UIImage(named: "Earth18.jpg")!,
UIImage(named: "Earth19.jpg")!,
UIImage(named: "Earth20.jpg")!,
UIImage(named: "Earth21.jpg")!,
UIImage(named: "Earth22.jpg")!,
UIImage(named: "Earth23.jpg")!,
UIImage(named: "Earth24.jpg")!,
UIImage(named: "Earth25.jpg")!,
UIImage(named: "Earth26.jpg")!,
UIImage(named: "Earth27.jpg")!,
UIImage(named: "Earth28.jpg")!,
UIImage(named: "Earth29.jpg")!,
UIImage(named: "Earth30.jpg")!,
UIImage(named: "Earth31.jpg")!
]
slideShow1.animationDuration = 93
slideShow1.startAnimating()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
}
*/
}
With all of the images you are loading, it's likely you are running out of memory on the phone - I had this problem recently. No error, but sometimes I did see "low memory warning" on the debug console.
To diagnose it, make an array of the image names, loop over that to populate an array of UIImage's, but check that they load as you go. Then you can set that as the animationImages. So,
let imageNames = [
"Earth1.jpg",
"Earth2.jpg",
// ...
"Earth31.jpg"
]
var images = [UIImage]()
for name in imageNames
{
guard let image = UIImage(named: name) else {
NSLog("Failed to load image \(name)")
continue
}
images.append(image)
}
slideShow1.animationImages = images
You can set a breakpoint at the continue to see how far you are getting.
Assuming that is what's going on, to fix the problem, you will need to use an image editing program make the images smaller (in terms of pixels). In my case, my images were photographs taken at 4000x6000 pixels; I found that resizing them to be the size they would actually be displayed made a difference. Also, saving the files with 50% loss made them about 15% of the original size (in terms of bytes), which made them load a lot faster. I showed the original and resized/lossy images to colleagues, they couldn't see a difference (one thought it was a joke of some kind).
I had a similar problem with an App I made with a large number of images (100). Their existence also slowed the app down somewhat. When I reduced the file size of each image - without affecting image Quality. It worked!! Easily done in 'Preview' on Apple.
Solution:
Open the images in mac OSX with 'Preview'
Goto menu at top... 'Tools'...'Adjust Size'
Reduce the pixel size of the image + Save
Put the newly saved images back into Xcode for your App
Reducing the file sizes by 40% without seeing any reduction in image quality on App worked!
Related
I have a tableViewCell with image view.
Even though I clear the imageView and set the image I get the below unintended behaviour, where one image is overlaid over the other.
Please advice how this could be resolved.
Code on Resetting Image in ImageView:
self.profileImageLabelTVCell.cellImageView.image = nil // Clearing previous image
self.profileImageLabelTVCell.cellImageView.image = image
Here is the link for the tableViewCell code and roundedImageView class, that I've used. (Since SO did not allow the entire code to be posted here)
https://gist.github.com/pravishanth/14cdb8bf14dd8899b081bdc97988b985
Add your reset image code to the TableViewCell's
func prepareForReuse() method.
override func prepareForReuse() {
super.prepareForReuse()
cellImageView.image = nil
}
I want to set a background image behind everything that already exists in the view.
I tried :
override func viewDidLoad() {
super.viewDidLoad()
let backgroundImage = UIImageView(frame: UIScreen.main.bounds)
backgroundImage.image = UIImage(named: "bg-img")
backgroundImage.contentMode = UIViewContentMode.scaleAspectFill
self.view.insertSubview(backgroundImage, at: 0)
}
But it didn't work (it didn't even add the img). So I imported the ImageView component and added the image using the Xcode GUI and it did add the image but is on top of everything. I have tried several Stack Overflow answers (that's where I get the above code from), but I didn't have any luck.
My folder structure looks like this:
My image is inside the Assets.xcassets folder.
UPDATE:
I restarted Xcode and my code worked.
Setting the subviews' frames in viewDidLoad is not a good practice because subviews aren't yet correctly placed at that point. You should set the frames of subviews in viewDidLayoutSubviews, which is called after the self.view and it's subviews are correctly laid out.
Your ViewController:
var backgroundImage: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
self.backgroundImage = UIImageView(image: UIImage(named: "bg-img"))
self.backgroundImage.contentMode = .scaleAspectFill
self.view.insertSubview(self.backgroundImage, at: 0)
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
self.backgroundImage.frame = self.view.bounds
}
All you have to do is give the full name of the jpg image:
backgroundImage.image = UIImage(named: "bg-img.jpg")
If it were placed in Assets.xcassets, or a png file, then specifying the extension would have been optional.
Sometimes all you need is to Clean your Build Folder ⇧⌘K, or just restart Xcode.
We can set the image of the button to be of aspect fill by :
imageView?.contentMode = UIViewContentMode.ScaleAspectFill
but since I am dealing with the background image, I'm not sure how to access contentMode for the background image.
I know that instead of doing so, I can create a custom class where the image can be centered with the button text centered on top, then set the imageview as aspect fill with the above code, but that seems like a lot more work to achieve the same result.
What's the best way to accomplish this?
It's been a long time since question asked but anyone who wonder,If you set your button image using setImage() it will show the image with its width and height no matter you scale, but If you set using setBackgroundImage() It will be just fine,didn't even need to scale
For whoever stumbles across this:
The UIImageView that contains backgroundImage is not publicly accessible. See https://developer.apple.com/documentation/uikit/uibutton, there is no such property. I do not why Apple protected that.
First solution
However you can filter your UIButton for all the UIImageViews and set their property to .scaleAspectFit:
override func layoutSubviews() {
super.layoutSubviews()
button
.subviews
.filter { $0 is UIImageView }
.forEach { $0.contentMode = .scaleAspectFit }
}
Second solution
The first solution could possibly conflict with any other UIImageViews you have in your UIButton. So you could also filter only for the exact background image:
override func layoutSubviews() {
super.layoutSubviews()
let backgroundImage = UIImage(named: "backspace-button")
let backgroundImageView = button
.subviews
.filter { $0 is UIImageView }
.first(where: { ($0 as? UIImageView)?.image == backgroundImage}) as? UIImageView
backgroundImageView?.contentMode = .scaleAspectFit
}
I would not recommend to access the backgroundImageView by its array index. There is no guarantee that this order will be like that forever.
I am developing an app, where I create buttons programmatically. When I click a button, it will request data from a database and show it in another view.
I use button.tag to determine what data to request, and I can only get the tag once the button is clicked.
However when I click the button, it shows nothing the first time. I must click it again to see the data which I want.
override func viewDidLoad() {
super.viewDidLoad()
//parseJSON(tagId)
createButton()
// Do any additional setup after loading the view, typically from a nib.
}
func createButton(){
var j:CGFloat=60
for var i:Int = 0 ; i < myImages.count;i = i+1 {
let myButton = UIButton()
myButton.setImage(UIImage(named: "carasusto.jpg"), forState: UIControlState.Normal)
myButton.setTitleColor(UIColor.blueColor(), forState: .Normal)
myButton.frame = CGRectMake(j, j+60, 50, 50)
myButton.tag = i //assign a tag to every button
myButton.addTarget(self, action: "segueToCreate:", forControlEvents: UIControlEvents.TouchUpInside)
self.view.addSubview(myButton)
j=j+60
print(myImages[i])
}
}
and
#IBAction func segueToCreate(sender: UIButton){
tagId = String(sender.tag)//tagId needs to fetch the information
parseJSON(tagId)
performSegueWithIdentifier("segueToView", sender:self)
}
func parseJSON(tagID:String){
Alamofire.request(.GET, "http://smarttags-001-site1.btempurl.com/SmartTagsRequests.aspx", parameters: ["AjaxFunc":"GetTagAttr","TagID":"\(tagID)"]).validate().responseJSON{ response in
switch response.result{
case .Success:
if let value = response.result.value {
let json = JSON(value)
print("JSON: \(json)")
self.TagName = json[0]["TagName"].stringValue
NSLog("\(self.TagName)")
self.ContentTitle = json[0]["ContentTitle"].stringValue
NSLog("\(self.ContentTitle)")
}
case .Failure(let error):
print(error)
}enter code here
}
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
var ViewTest : ViewTwo = segue.destinationViewController as! ViewTwo
var TagNameLabel = UILabel()
TagNameLabel.frame = CGRectMake(74, 112, 182, 64)
ViewTest.view.addSubview(TagNameLabel)
TagNameLabel.text = TagName
var ContentTitleLabel = UILabel()
ContentTitleLabel.frame = CGRectMake(74, 180, 182, 64)
ViewTest.view.addSubview(ContentTitleLabel)
ContentTitleLabel.text = ContentTitle
}
}
To follow up to MirekE's answer, here are some other steps you may want to consider:
Consider using Auto Layout in place of hard-coded frames, so your UI would adapt to different size classes.
Consider alternate approaches for showing an interactive list (of images) instead of programmatically adding buttons. For example, you could use a prototype (table view or collection view) cell. Cells are selectable and can take the place of a button. Other benefits include:
A scrollable container, should you have more buttons than would fit on screen.
A single segue (or selection) is handled by the storyboard prototype cell, instead of needing to make each programmatic "button" perform a segue (or action). Since you'd know which cell was selected, you'd no longer need a tag to figure that out.
Consider passing parameters to your destination view controller, instead of trying to instantiate and create controls in prepareForSegue. The destination's view is not loaded at that point.
Consider allowing the UI to feel more responsive, such as by performing the segue and showing placeholder details which you can then update once the network request completes. Otherwise, the user may have to wait for the network response before the segue occurs (which might make them think nothing happened and needlessly tap again, leading to an additional network request).
In general, Apple (or someone else) has already provided some way to do what you want, ideally leading to less code that you have to write, debug, test, and maintain to accomplish what you want your app to do.
Most likely cause of the problem is your call to parseJson followed by prepareForSegue. parseJson is asynchronous and you don't get data back from your service before prepareForSegue is called. As a first step, move the prepareForSegue to the completion block of the parseJson.
new to app development.
What I want to do is allow the user to take 2 photos from the Camera and review them. Will then code around those two photos. Preference is just to take from Camera but also could be from PhotoLibrary.
My thought process around this was, click a button to take first photo, then display that photo in a UIPageViewController then allow them to take another photo by pushing a Button to call the camera again, then that Photo would be the second as part of the UIPageViewController. So the user can swipe with the 2 dots at the bottom to review, then select confirm.
So what I did was follow the UIImagePickerController to be able to allow the user to select from library or Camera and also implemented the UIPageViewController. But having trouble putting the two together.
When I get an image from Camera I think I need to save it. The pageviewcontroller gets the filenames from a NSMutableArray.
With the viewDidLoad... I call the Camera, and also the pageViewController. My problem is the pageviewcontroller gets data from pageImages[] but how do I populate the array with the image name string? Did I do it correctly below? I think i may have a problem in that does the pageviewcontroller controller automatically update once i dismiss the picker otherwise will never get the image from the camera. I am thinking i may have to call camera then pass the data to the view controller through segues?
Other thought I was having was to have a view controller that just displays the taken image, saves it, adds filename to array, then when user takes another photo, the image is saved and added to array and then passed through segue to the pageviewcontroller for the user to be able to scroll through both and either confirm or delete. Thoughts?
override func viewDidLoad() {
super.viewDidLoad()
createPhoto()
self.pageViewController = self.storyboard?.instantiateViewControllerWithIdentifier("PhotoPageViewControllerStoryBoard") as! UIPageViewController
self.pageViewController.dataSource = self
var startVc = self.pageViewControllerAtIndex(0) as PhotoContentPageViewController
var viewControllers = NSArray(object: startVc)
self.pageViewController.setViewControllers(viewControllers as? [UIViewController], direction: .Forward, animated: true, completion: nil)
self.pageViewController.view.frame = CGRectMake(0, -10, self.view.frame.size.width, self.view.frame.size.height-30)
self.addChildViewController(self.pageViewController)
self.view.addSubview(self.pageViewController.view)
self.pageViewController.didMoveToParentViewController(self)
}
func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : AnyObject]) {
var newImage: UIImage
if let possibleImage = info["UIImagePickerControllerEditedImage"] as? UIImage {
newImage = possibleImage
} else if let possibleImage = info["UIImagePickerControllerOriginalImage"] as? UIImage {
newImage = possibleImage
} else {
return
}
if (picker.sourceType == UIImagePickerControllerSourceType.Camera) {
let imageName = NSUUID().UUIDString
let imagePath = getDocumentsDirectory().stringByAppendingPathComponent(imageName)
if let jpegData = UIImageJPEGRepresentation(newImage, 80) {
jpegData.writeToFile(imagePath, atomically: true)
}
pageImages!.addObject(imageName)
}
dismissViewControllerAnimated(true, completion: nil)
}
ok, think I got it, i changed the array to pass to the pageviewcontroller from the contentviewcontroller UIImages instead of filename strings.
Also, since the array was nil i didn't start the pageviewcontroller until after the first image was taken or chosen.
Off to battle the next set of code, to actually do something with the two image. most likely will need some help in the future... near future. Thanks.