Xcode 9: Getting More Information On Memory Leak - ios

After learning that a memory leak may be the cause of my iOS app crashing hours after being installed onto my phone, I've watched and read videos/articles surrounding memory leaks, specifically in Xcode and how to debug them. I've been trying to use the Memory Debugger and Xcode leaks instrument with no luck. I am wondering, how do I find the source of my leaks? The screenshot I attached shows one of the many leaks and it never lets me dig deeper than what the picture shows. In videos, Xcode takes them to the line of code causing the issue using the backtrace, however I have not been able to do that in any of the cases. I am also noticing the leaks are coming from the UIKIT, is this normal? Appreciate any help as I am fairly new to Xcode.
Here is the image.
Here is some code from my initial log in view controller. In the second image you'll see that the only view that has a memory leak is this one, however - I can't dig deeper on this either.
Second image of memory leak from InitialLogInVC
import UIKit
class IntialLoginInViewController: UIViewController {
#IBOutlet weak var backgroundAlbumArt: UIImageView!
#IBOutlet weak var foregroundAlbumArt: UIImageView!
#IBOutlet weak var musicNameLabel: UILabel!
private weak var imageOne = UIImage(named: "taste.jpg")
private weak var imageTwo = UIImage(named: "NavReckless.jpg")
private weak var imageThree = UIImage(named: "watch.jpg")
private weak var imageFour = UIImage(named: "juicewrld.jpg")
lazy var imageInformation = [(name:"Taste - Tyga", image:imageOne),(name:"Reckless - Nav", image: imageTwo),(name:"Watch - Travis Scott", image: imageThree), (name:"Goodbye & Good Riddance - Juice Wrld", image: imageFour)]
private var currentIndex = 0
static var spotifySession: AnyObject?
override func viewDidLoad() {
super.viewDidLoad()
foregroundAlbumArt.image = imageOne
backgroundAlbumArt.image = imageOne
musicNameLabel.text = imageInformation[0].name
let _ = Timer.scheduledTimer(timeInterval: 10.0, target: self, selector: #selector(imageRefresh), userInfo: nil, repeats: true)
}
override var preferredStatusBarStyle : UIStatusBarStyle {
return .lightContent
}
#objc func imageRefresh(){
if currentIndex == imageInformation.count - 1 {
currentIndex = 0
}
else {
currentIndex += 1
}
//Update Label
UIView.transition(with: self.musicNameLabel, duration: 2.0, options: [.transitionCrossDissolve] , animations: {
self.musicNameLabel.text = self.imageInformation[self.currentIndex].name
}, completion: nil)
//Update foreground Album
UIView.transition(with: self.foregroundAlbumArt, duration: 2.0, options: [.transitionCrossDissolve] , animations: {
self.foregroundAlbumArt.image = self.imageInformation[self.currentIndex].image
}, completion: nil)
//Update background Album
UIView.transition(with: self.backgroundAlbumArt, duration: 2.0, options: [.transitionCrossDissolve] , animations: {
self.backgroundAlbumArt.image = self.imageInformation[self.currentIndex].image
}, completion: nil)
}
}

try this
//Update Label
UIView.transition(with: self.musicNameLabel, duration: 2.0, options: [.transitionCrossDissolve] , animations: { [weak self] in
guard let `self` = self else { return }
self.musicNameLabel.text = self.imageInformation[self.currentIndex].name
}, completion: nil)
//Update foreground Album
UIView.transition(with: self.foregroundAlbumArt, duration: 2.0, options: [.transitionCrossDissolve] , animations: { [weak self] in
guard let `self` = self else { return }
self.foregroundAlbumArt.image = self.imageInformation[self.currentIndex].image
}, completion: nil)
//Update background Album
UIView.transition(with: self.backgroundAlbumArt, duration: 2.0, options: [.transitionCrossDissolve] , animations: { [weak self] in
guard let `self` = self else { return }
self.backgroundAlbumArt.image = self.imageInformation[self.currentIndex].image
}, completion: nil)

Related

How do I save a users check mark selection in iOS

I am currently trying to save a users check mark selection in iOS. I have a custom made empty check box and a box with the check mark. I have gotten to change when I click it, but I am unsure how to save the checkmark to user defaults after the app is closed. I know how to do this for text and slider buttons but I just can't figure it out for these images. I am programming in Swift and your help is very much appreciated. I already have some of the code setup but I could be wrong on it all.
import UIKit
class TableViewChallengeTrackerTableViewController: UITableViewController {
//checkboxes
#IBOutlet weak var checkBoxButton1: UIButton!
var saveCheckBoxes = UserDefaults.standard
//saving keys
let checkBoxSave01 = "checkBoxSave01"
override func viewDidLoad() {
super.viewDidLoad()
//check box #1 images
checkBoxButton1.setImage(UIImage(named:"greyUnchecked"), for: .normal)
checkBoxButton1.setImage(UIImage(named:"greyChecked"), for: .selected)
//loading the saved check box
if saveCheckBoxes.object(forKey: checkBoxSave01) != nil{
}
}
//check box changed/check box had heptic/saving user selection
let impact = UIImpactFeedbackGenerator(style: UIImpactFeedbackStyle.medium)
#IBAction func checkBoxTapped(_ sender: UIButton) {
saveCheckBoxes.set(sender.currentImage, forKey: checkBoxSave01)
impact.impactOccurred()
UIView.animate(withDuration: 0.0, delay: 0.0, options: .curveLinear, animations: {
sender.transform = CGAffineTransform(scaleX: 0.0, y: 0.0)
}) { (success) in
UIView.animate(withDuration: 0.0, delay: 0.0, options: .curveLinear, animations: {
sender.isSelected = !sender.isSelected
sender.transform = .identity
}, completion: nil)
}
}
}
Save the value as a boolean, not as the image. Then when you load this view controller, get that boolean value from defaults. If it is true, check the checkbox. If it is false, do not check the checkbox.
UserDefaults.standard.set(true, forKey: "checkBoxSave01")
When trying to setup the checkboxes later, you can do something like this in viewDidLoad:
if UserDefaults.standard.bool(forKey: "checkBoxSave01") == true {
// Configure the checkbox as checked
} else {
// Configure the checkbox as unchecked
}
bool(forKey:) will return true if the value set at that key is true. It will return false if the value was set to false or if there is no value at that key.
This might help you. And you can easily modify it to suit your needs.
enum UserDefaultsKeys : String {
case someBox
case anotherBox
}
extension UserDefaults{
func setSomeBox(value : Bool){
set(value, forKey: UserDefaultsKeys.someBox.rawValue)
}
func getSomeBox() -> Bool {
return bool(forKey: UserDefaultsKeys.someBox.rawValue)
}
func setAnotherBox(value : Bool){
set(value, forKey: UserDefaultsKeys.anotherBox.rawValue)
}
func GetAnotherBox() -> Bool {
return bool(forKey: UserDefaultsKeys.anotherBox.rawValue)
}
}

Swift: Load Photos images into app

I have an app which shows a screensaver when it detects that there is no user action. Currently, it is working as the images for the screensaver are preloaded into the app.
But ultimately I want the screensaver to access and show the images inside Photos as we would save promotion images into Photos and these images are subjected to frequent changes.
I am stuck and do not know how to proceed to access and read images from Photos.
Please help. :(
import UIKit
import Photos
class ScreenSaverViewController: UIViewController {
#IBOutlet weak var ssImage: UIImageView!
var timer = Timer()
var count = 0
var arrayPhoto: [UIImage] = [
UIImage(named:"0.jpg")!,
UIImage(named:"1.jpg")!,
UIImage(named:"2.jpg")!,
UIImage(named:"3.jpg")!
]
var images = [PHAsset]()
func nextImage() {
let currentIndex = arrayPhoto.index(of: ssImage.image ?? UIImage()) ?? -1
var nextIndex = currentIndex + 1
nextIndex = arrayPhoto.indices.contains(nextIndex) ? nextIndex : 0
UIView.transition(with: ssImage, duration: 0.5, options: .transitionCrossDissolve, animations: { self.ssImage.image = self.arrayPhoto[nextIndex] }, completion: nil)
}
func scheduledTimer(){
timer = Timer.scheduledTimer(timeInterval: 5, target: self, selector: (#selector(nextImage)), userInfo: nil, repeats: true)
count = 1
}
override func viewDidLoad() {
super.viewDidLoad()
if count == 0 {
ssImage.image = UIImage(named: "0.jpg")
}
scheduledTimer()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
timer.invalidate()
self.dismiss(animated: true, completed: nil)
}
}
You can directly use Photos framework. I encourage you to read documentation first: https://developer.apple.com/documentation/photos
Anyway, to get images, you can do it like this:
func getImages() {
let assets = PHAsset.fetchAssets(with: PHAssetMediaType.image, options: nil)
assets.enumerateObjects({ (object, count, stop) in
self.images.append(object)
})
//In order to get latest image first, we just reverse the array
images.reverse()
}
Credits: Swift 3: Load photos from Photos/Camera Roll without using UIImagePickerController
I think you should provide a button in your app, tapping on which will open UIImagePickerController with source type .photos. The user of the app can select the images he wish should be included in the screen saver.
Implement the protocols UIImagePickerController & UINavigationControllerDelegate, particularly the function func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : AnyObject]) in which you can access the selected images. You may then save these images to the Documents folder of your app and use them while showing the screen saver.

Adding a Video file from library to application?

I am currently working on uploading Photos and videos to my app. So far I got the photo uploading done, but when I access my library on my iPad, I can't see my videos or choose them. Anyone who knows how I can do this?
This is my code so far:
Class SixthViewController: UIViewController, UIImagePickerControllerDelegate, UINavigationControllerDelegate
{
#IBOutlet weak var logUd: UIButton!
#IBOutlet weak var myImageView: UIImageView!
#IBOutlet weak var vælgFiler: UIButton!
var SelectedAssets = [PHAsset]()
var PhotoArray = [UIImage]()
#IBOutlet weak var upload: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
logUd.backgroundColor = UIColor.white
logUd.layer.cornerRadius = 7
logUd.setTitleColor(UIColor.black, for: .normal)
vælgFiler.backgroundColor = UIColor.white
vælgFiler.layer.cornerRadius = 7
vælgFiler.setTitleColor(UIColor.black, for: .normal)
upload.backgroundColor = UIColor.white
upload.layer.cornerRadius = 7
upload.setTitleColor(UIColor.black, for: .normal)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func logUd(_ sender: Any) {
dismiss(animated: true, completion: nil)
}
#IBAction func upload(_ sender: Any) {
//myImageUploadRequest()
}
#IBAction func vælgFiler(_ sender: Any) {
let vc = BSImagePickerViewController()
self.bs_presentImagePickerController(vc, animated: true,
select: { (assets: PHAsset) -> Void in
},deselect: { (asset: PHAsset) -> Void in
},cancel: {(assets: [PHAsset]) -> Void in
}, finish: {(assets: [PHAsset]) -> Void in
for i in 0..<assets.count{
self.SelectedAssets.append(assets[i])
}
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.PhotoArray.append(newImage! as UIImage)
}
self.myImageView.animationImages = self.PhotoArray
self.myImageView.animationDuration = 10.0
self.myImageView.startAnimating()
}
print("complete photo array \(self.PhotoArray)")
}
If you would want BSImagePicker to be able to show videos as well as photos you will have to change the source of the library.
There is a file called PhotosViewController that displays the grid of photos. Inside is initializePhotosDataSource method that filters out all videos and displays only photos with:
fetchOptions.predicate = NSPredicate(format: "mediaType = %d", PHAssetMediaType.image.rawValue)
if you were to delete this predicate from fetchOptions the library will (and I have tested this) show videos along with photos...

AVPlayerViewController not deallocating immediately

I am playing a video in my app in TVos. I am using AVPlayerViewController to play the video. But when i press Menu button on Apple Tv remote i goes back to the view controller from which i came here but video continues to play and after 8 or 10 second it gets deallocated. This is really a bad bug and i am stuck on this for few days. Any help will be highly appreciated.
Here is my code for the view controller.
import Foundation
import UIKit
import AVKit
class ViewController : UIViewController {
var avplayerVC : AVPlayerViewController?
var recentlyWatchedTimer : NSTimer?
var lessonToWatch : Lesson?
override func viewDidLoad() {
super.viewDidLoad()
if let urlVideo = lessonToWatch?.lessonurl {
let activityIndicator = UIActivityIndicatorView(frame: CGRectMake(self.view.frame.size.width / 2.0, self.view.frame.size.height / 2.0, 30.0, 30.0))
let asset : AVURLAsset = AVURLAsset(URL: NSURL.init(string: urlVideo)!, options: nil)
let keys = ["playable"];
avplayerVC = AVPlayerViewController()
weak var weakSelf = self
asset.loadValuesAsynchronouslyForKeys(keys) { () -> Void in
dispatch_async(dispatch_get_main_queue(), { () -> Void in
weakSelf!.avplayerVC?.player = AVPlayer(playerItem: AVPlayerItem(asset: asset))
weakSelf!.avplayerVC?.player?.seekToTime(kCMTimeZero)
print("Status 1: " + "\(self.avplayerVC?.player?.status.rawValue)")
print(self.view?.frame)
// doesn't work
weakSelf!.avplayerVC?.view.frame = self.view.frame
activityIndicator.stopAnimating()
activityIndicator.removeFromSuperview()
weakSelf!.view.addSubview((self.avplayerVC?.view!)!)
weakSelf!.avplayerVC?.player?.play()
weakSelf!.recentlyWatchedTimer = NSTimer.scheduledTimerWithTimeInterval(20.0, target: self, selector: "addToRecentlyWatched" , userInfo: nil, repeats: false)
})
}
print("In LessonPlayViewController View Did Load")
self.view.addSubview(activityIndicator)
activityIndicator.startAnimating()
}
}
func addToRecentlyWatched() {
if let lesson = lessonToWatch {
DataManager.sharedInstance.addRecentlyWatch(lesson)
}
recentlyWatchedTimer?.invalidate()
}
deinit {
print("deinit")
avplayerVC?.view.removeFromSuperview()
avplayerVC?.player = nil
avplayerVC = nil
}
// MARK : AVPlayerViewControllerDelegate
}
You want to use weakSelf in all places inside the async callback block, but you're printing self.view.frame at once place.
You should use a capture list to make sure all references to self are weak within the closure. Then use a guard to check that the references are still valid within the closure.
import Foundation
import UIKit
import AVKit
import AVFoundation
class ViewController : UIViewController {
var avplayerVC : AVPlayerViewController?
var recentlyWatchedTimer : NSTimer?
var lessonToWatch : Lesson?
override func viewDidLoad() {
super.viewDidLoad()
if let urlVideo = lessonToWatch?.lessonurl {
let activityIndicator = UIActivityIndicatorView(frame: CGRectMake(self.view.frame.size.width / 2.0, self.view.frame.size.height / 2.0, 30.0, 30.0))
let asset : AVURLAsset = AVURLAsset(URL: NSURL.init(string: urlVideo)!, options: nil)
let keys = ["playable"];
avplayerVC = AVPlayerViewController()
asset.loadValuesAsynchronouslyForKeys(keys) { [weak self] in
dispatch_async(dispatch_get_main_queue()) { [weak self] in
guard let vc = self, playerVC = vc.avplayerVC else {
return
}
playerVC.player = AVPlayer(playerItem: AVPlayerItem(asset: asset))
playerVC.player?.seekToTime(kCMTimeZero)
print("Status 1: " + "\(playerVC.player?.status.rawValue)")
print(vc.view?.frame)
// doesn't work
playerVC.view.frame = vc.view.frame
activityIndicator.stopAnimating()
activityIndicator.removeFromSuperview()
vc.view.addSubview(playerVC.view)
playerVC.player?.play()
vc.recentlyWatchedTimer = NSTimer.scheduledTimerWithTimeInterval(20.0, target: vc, selector: "addToRecentlyWatched" , userInfo: nil, repeats: false)
}
}
print("In LessonPlayViewController View Did Load")
self.view.addSubview(activityIndicator)
activityIndicator.startAnimating()
}
}
func addToRecentlyWatched() {
if let lesson = lessonToWatch {
DataManager.sharedInstance.addRecentlyWatch(lesson)
}
recentlyWatchedTimer?.invalidate()
}
deinit {
print("deinit")
avplayerVC?.view.removeFromSuperview()
avplayerVC?.player = nil
avplayerVC = nil
}
// MARK : AVPlayerViewControllerDelegate
}
More on capture lists:
https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/AutomaticReferenceCounting.html#//apple_ref/doc/uid/TP40014097-CH20-ID57
Edit
Also note that you should explicitly stop the video playback when leaving the view controller and not rely on the player to stop the playback as part of its deallocation.
I.e. in viewDidDisappear or alike.

Typewriter effect text animation

I'm trying to create a typewriter animation effect with a UILabel, but can't find any answers. Is the UILabel the correct object to use? I want the text to print to the screen an array of strings like, "Logging in... Opening Folder... Rebooting system.." etc. I should mention that I'm new to coding and I've tried searching the Documentation and API reference but no luck. I'm currently learning SWIFT if thats worth mentioning
Based on this Answer:
Letter by letter animation for UILabel?
I've updated it to Swift 4 and solved the CPU animation problem with DispatchWorkItem in order to create a queue.
Swift 4
extension UILabel {
func setTextWithTypeAnimation(typedText: String, characterDelay: TimeInterval = 5.0) {
text = ""
var writingTask: DispatchWorkItem?
writingTask = DispatchWorkItem { [weak weakSelf = self] in
for character in typedText {
DispatchQueue.main.async {
weakSelf?.text!.append(character)
}
Thread.sleep(forTimeInterval: characterDelay/100)
}
}
if let task = writingTask {
let queue = DispatchQueue(label: "typespeed", qos: DispatchQoS.userInteractive)
queue.asyncAfter(deadline: .now() + 0.05, execute: task)
}
}
}
Usage
label.setTextWithTypeAnimation(typedText: text, characterDelay: 10) //less delay is faster
Swift 5
func setTyping(text: String, characterDelay: TimeInterval = 5.0) {
self.text = ""
let writingTask = DispatchWorkItem { [weak self] in
text.forEach { char in
DispatchQueue.main.async {
self?.text?.append(char)
}
Thread.sleep(forTimeInterval: characterDelay/100)
}
}
let queue: DispatchQueue = .init(label: "typespeed", qos: .userInteractive)
queue.asyncAfter(deadline: .now() + 0.05, execute: writingTask)
}
Usage
label.setTyping(text: "Your text")
update: Xcode 7.0 GM • Swift 2.0
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var myTypeWriter: UITextField!
let myText = Array("Hello World !!!".characters)
var myCounter = 0
var timer:NSTimer?
func fireTimer(){
timer = NSTimer.scheduledTimerWithTimeInterval(0.5, target: self, selector: "typeLetter", userInfo: nil, repeats: true)
}
func typeLetter(){
if myCounter < myText.count {
myTypeWriter.text = myTypeWriter.text! + String(myText[myCounter])
let randomInterval = Double((arc4random_uniform(8)+1))/20
timer?.invalidate()
timer = NSTimer.scheduledTimerWithTimeInterval(randomInterval, target: self, selector: "typeLetter", userInfo: nil, repeats: false)
} else {
timer?.invalidate()
}
myCounter++
}
override func viewDidLoad() {
super.viewDidLoad()
fireTimer()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
I have written a subclass of UILabel called CLTypingLabel, available on GitHub. This should do what you want.
After installing CocoaPods, add the following like to your Podfile to use it:
pod 'CLTypingLabel'
Sample Code
Change the class of a label from UILabel to CLTypingLabel;
#IBOutlet weak var myTypeWriterLabel: CLTypingLabel!
At runtime, set text of the label will trigger animation automatically:
myTypeWriterLabel.text = "This is a demo of typing label animation..."
You can customize time interval between each character:
myTypeWriterLabel.charInterval = 0.08 //optional, default is 0.1
You can pause the typing animation at any time:
myTypeWriterLabel.pauseTyping() //this will pause the typing animation
myTypeWriterLabel.continueTyping() //this will continue paused typing animation
Also there is a sample project that comes with cocoapods
my version of the typewriter effect animation using a timer:
var text = "text"
_ = Timer.scheduledTimer(
withTimeInterval: 0.1,
repeats: true
) { [weak self] timer in
let char = text.removeFirst()
self?.yourLabel.text?.append(char.description)
if text.isEmpty {
timer.invalidate()
}
}

Resources