Mysterious memory leak with UIActivity and UIActivityViewController - ios

I'm running into what looks like a memory leak using Swift on iOS.
Here's a pared-down runnable example — you can tap the screen to show a share sheet, and BadActivity will leak every time.
import UIKit
class ViewController: UIViewController {
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
let activity = ShareController(activityItems: [""], applicationActivities: [BadActivity(), GoodActivity()])
self.showViewController(activity, sender: nil)
}
}
class ShareController: UIActivityViewController {
deinit { print("ShareController deinit.") }
}
class GoodActivity: UIActivity {
override func activityTitle() -> String { return "Good" }
deinit { print("Good deinit. This is printed.") }
}
class BadActivity: UIActivity {
override func activityTitle() -> String { return "Bad" }
deinit { print("Bad deinit. This is never printed.") }
override func canPerformWithActivityItems(activityItems: [AnyObject]) -> Bool {
return true
}
override func performActivity() {
self.activityDidFinish(true)
}
}
Regardless of whether you cancel the dialog or press export, BadActivity is instantiated but never deallocated – the console output is ShareController deinit. Good deinit.
If you make BadActivity.canPerformWithActivityItems return false instead of true, however, it deallocates normally.

Related

in <new> SwiftUI 5.3 with no ViewControllers where do you override motion events

Typically in a ViewController - but in the new (5.3 & iOS 14) SwiftUI with declarative code... where does one put these methods? It doesn't appear that one can just drop code into a view builder...
e.g.
import CoreMotion
override func motionEnded(_ motion: UIEventSubtype, with event: UIEvent?) {
if motion == .motionShake {'
print ("SHAKE GESTURE DETECTED")
}
}
Here's the answer I got from Twitter's Majid Jabrayilov #mecid !! Thanks!
struct ContentView: View {
var body: some View {
Text("Shake Me!")
MotionView(handler: {
print(" - earthquake!")
})
}
}
Note that the 'MotionView' has no screen representation - but is a view.
import SwiftUI
struct MotionView: UIViewControllerRepresentable {
typealias UIViewControllerType = MotionViewController
let handler: () -> Void
func makeUIViewController(context: Context) -> MotionViewController {
print("MotionView::makeUIViewController called. Returning Controller with 'no-Op' handler.")
return MotionViewController(handler: handler)
}
func updateUIViewController(_ uiViewController: MotionViewController, context: Context) {
print("updateUIViewController called...")
}
}
Then behind that view is the controller... this controller overrides the motion events and injects our 'handler()' ... it's just the print statement in the demo.
import Foundation
import UIKit
final class MotionViewController: UIViewController {
let handler: () -> Void
init(handler: #escaping () -> Void) {
self.handler = handler
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func becomeFirstResponder() -> Bool {
return true
}
override func motionEnded(_ motion: UIEvent.EventSubtype, with event: UIEvent?) {
if motion == .motionShake {
print("\t\tinside MotionViewController::motionEnded() shaken not stired.")
handler()
}
}
}
The controller overrides the 'motionEnded' method and if it detects a shake event (predefined by Apple) calls our 'handler()'
I wonder if this style of injection / overriding is what the Swift designers intend with SwiftUI declarative coding??

How to make Singleton in Swift

I want to use Singleton to show ads, but it doesn't work well.
When I don't use Singleton and use only ViewController, it works well.(can through "vampLoadStart" and "vampDidReceive")
How can I solve it?
Pattern1: when I use Singleton (can't load and show ad)
VAMPAdReward.swift
import Foundation
import UIKit
import VAMP
class VAMPAdReward: NSObject,VAMPDelegate{
static let sharedInstance = VAMPAdReward()
var adReward:VAMP!
override init() {
super.init()
}
func loadAdReward(parentViewController: UIViewController) {
adReward = VAMP()
adReward.setPlacementId("26812") //test ID
adReward.delegate = self
adReward.setRootViewController(self)
}
func showAdReward(){
if adReward.isReady() {
print("show ad")
adReward.show()
}else{
print("couldn't show ad")
}
}
func vampLoadStart(_ placementId: String!, adnwName: String!) {
print("start loading")
}
func vampDidReceive(_ placementId: String!, adnwName: String!) {
print("finished loading")
}
}
ViewController
import UIKit
class ViewController: UIViewController {
var adReward: VAMPAdReward!
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = UIColor.white
VAMPAdReward.sharedInstance.loadAdReward(parentViewController: self)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
//when touch screen, show Ad
VAMPAdReward.sharedInstance.showAdReward()
}
}
Pattern2: when I don't use Singleton (can load and show ad)
import UIKit
import VAMP
class ViewController: UIViewController, VAMPDelegate {
var ad: VAMP!
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = UIColor.white
//load ad
ad = VAMP()
ad.setPlacementId("59755") //test ID
ad.delegate = self
ad.setRootViewController(self)
ad.load()
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
ad.show()
}
func vampLoadStart(_ placementId: String!, adnwName: String!) {
print("start loading") //through
}
func vampDidReceive(_ placementId: String!, adnwName: String!) {
print("finished loading") //through
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Follow these step for accomplish your singleton class
// MARK: - Singleton
final class Singleton {
// Can't init is singleton
private init() { }
// MARK: Shared Instance
static let shared = Singleton()
// MARK: Local Variable
var emptyStringArray : [String] = []
}
Correct method in singleton
func loadAdReward(parentViewController: UIViewController) {
adReward = VAMP()
adReward.setPlacementId("26812") //test ID
adReward.delegate = self
adReward.setRootViewController(parentViewController)
adReward.load()
}

Parent variable is not updated for the ChildViewController

I have a class which is inherited from BaseViewController.swift. I have defined a flag for core data changes tracking:
var coreDataUpdated: Bool?
I've also added an observer for core data changes in viewWillAppear of BaseViewController.swift
//Core data update
NotificationCenter.default.addObserver(self,
selector: #selector(self.CoreDataUpadated),
name: .NSManagedObjectContextObjectsDidChange,
object: nil)
Now whenever I notified about changes in core data, I change coreDataUpdated variable to true
#objc func CoreDataUpadated() {
self.coreDataUpdated = true
}
Now in my ChildViewController.swift in viewDidAppear when I check for coreDataUpdated it returns me nil
here is my complete code:
class BaseViewController: UIViewController {
var coreDataUpdated: Bool?
override func viewWillAppear(_ animated: Bool) {
//Core data update
NotificationCenter.default.addObserver(self,
selector: #selector(self.CoreDataUpadated),
name: .NSManagedObjectContextObjectsDidChange,
object: nil)
}
func CoreDataUpadated() {
self.coreDataUpdated = true
}
}
class ChildViewController: BaseViewController {
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
reloadData()
}
func reloadData() {
if super.coreDataUpdated ?? false { //I always get nil for
super.coreDataUpdated
tableView?.reloadData()
}
}
}
Notice: I'm working on Xcode 9 beta 5 and iOS 11 with swift 4. However this code works fine on the Xcode 8 and iOS 10.3 with swift 3.
Class inheritance does not mean that subclass instances inherit the values assigned to properties in superclass instances.
You have an instance of BaseViewController which has a coreDataUpdated property and instance of ChildViewController, which has a coreDataUpdated property since it inherits from BaseViewController, but it's property value is unrelated to the BaseViewController instance property value; they are different objects.
When you say self.coreDataUpdated = true you are setting the property on the BaseViewController instance.
When you say super.coreDataUpdated you are referring to the coreDataUpdated property in the ChildViewController instance that is defined by it's superclass; since this property has never been assigned a value it is nil.
Since ChildViewController does not override coreDataUpdated super.coreDataUpdated is the same as self.coreDataUpdated in an instance of ChildViewController, so you could re-write that if statement as:
func reloadData() {
if self.coreDataUpdated ?? false {
tableView?.reloadData()
}
}
Hopefully this makes it clearer as to why coreDataUpdated is nil
Rather than using your own Bool, it may be simpler to examine the hasChanges property of your NSManagedObjectContext instance.
Thanks to #Paulw11 comments, I found out with inheritance the value won't be shared. So I use KVC pattern as the solution. this is my code after change
enum coreDataState: String {
case inserted = "inserted"
case deleted = "deleted"
case updated = "updated"
var description: String {
switch self {
case .inserted: return "inserted"
case .deleted: return "deleted"
case .updated: return "updated"
}
}
}
class BaseViewController: UIViewController {
override func viewWillAppear(_ animated: Bool) {
//Core data update
NotificationCenter.default.addObserver(self,
selector: #selector(self.CoreDataUpadated),
name: .NSManagedObjectContextObjectsDidChange,
object: nil)
}
func CoreDataUpdated(_ notification: Notification) {
if let userDictionary = notification.userInfo as? Dictionary<String, Any> {
for KV in userDictionary {
if KV.key != "managedObjectContext" {
NSUserDefaultManager.SaveItem(KV.key, key: coreDataUpdateKey)
}
}
}
}
func CoreDataChanged() -> coreDataState? {
if let currentState = NSUserDefaultManager.LoadItem(coreDataUpdateKey) as? String {
NSUserDefaultManager.RemoveItem(coreDataUpdateKey)
return coreDataState(rawValue: currentState)
}
return nil
}
}
class ChildViewController: BaseViewController {
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
reloadData()
}
func reloadData() {
if super.CoreDataChanged() != .deleted {
loadData()
tableView?.reloadData()
}
}
}
Although it is possible to implement it with other patterns such as delegate or override the CoreDataUpdated function in child classes

Why I can't call a method stored in parent UIViewController from embedded UITableViewController?

I have a parent ui view controller and it has a method responsible for printing data to the console:
func printSomeData() {
print("printing some data")
}
It also has a container with embedded UITableViewController. The table itself has a pull to refresh functionality implemented and it prints the string when user pulls the table:
func refresh(refreshControl: UIRefreshControl) {
print("Refreshing!!")
refreshControl.endRefreshing()
}
Now I want to call printsomeData from the refresh method.
This is what I try:
parent UIViewController:
class MainMenu: UIViewController, printing{
func printSomeData() {
print("some date")
}
}
embedded UITableViewController:
protocol printing{
func printSomeData()
}
class MainMenuTableViewController: UITableViewController {
var delegate: printing?
func refresh(refreshControl: UIRefreshControl) {
print("Refreshing!!")
if let _ = delegate{
delegate?.printSomeData()
}
refreshControl.endRefreshing()
}
But now when I pull the table I only see Refreshing!!, there is no way I could see printing some data. What am I doing wrong?
Where are you assigning the delegate?
And write the optional method call as a single line
delegate?.printSomeData()
or like that:
if self.delegate != nil {
self.delegate!.printSomeData()
}
Inside MainMenu
override func viewDidLoad() {
super.viewDidLoad()
// tableViewController is placeholder for `MainMenuTableViewController` reference
tableViewController.delegate = self
}
If i have understand you correctly and the MainMenu has a ContainerView with MainMenuTableViewController than should this solve your problem:
class MainMenu: UIViewController, Printer {
func printSomeData() {
print("some date")
}
}
protocol Printer {
func printSomeData()
}
class MainMenuTableViewController: UITableViewController {
var printer: Printer? {
guard let printer = self.parentViewController as? Printer else {
return nil
}
return printer
}
func refresh(refreshControl: UIRefreshControl) {
print("Refreshing!!")
printer?.printSomeData()
refreshControl.endRefreshing()
}
}

Remote Control event in iOS with Swift

Trying to figure out how to read the Apple headphone's volume buttons to use as a trigger for the camera shutter (as the Apple Camera app does).
From the documentation on Remote Control Events,
Remote Control Received With Event, and this git repo, I've pieced together that I'll probably need an AVAudioPlayer object, .beginReceivingRemoteControlEvents(), and remoteControlReceivedWithEvent, along with making this view canBecomeFirstResponder() return true.
import UIKit
import AVFoundation
class ViewController: UIViewController, AVAudioPlayerDelegate {
var player: AVAudioPlayer!
override func viewDidLoad() {
super.viewDidLoad()
var session: AVAudioSession = AVAudioSession.sharedInstance()
session.setActive(true, error: nil)
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
println("viewDidAppear worked...")
self.becomeFirstResponder()
UIApplication.sharedApplication().beginReceivingRemoteControlEvents()
}
override func canBecomeFirstResponder() -> Bool {
return true
}
override func remoteControlReceivedWithEvent(event: UIEvent) {
let rc = event.subtype
println("does this work? \(rc.rawValue)")
//takePicture()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
I expected to get "does this work" when hitting the volume buttons on the headphones, instead I just see it adjust the headphone volume like normal. So I must be missing something, maybe with a delegate or AVSession?
I cross-posted this on r/swift, where I was told it probably requires playing audio (quoted straight from the documentation).
So while this isn't the ideal solution, it works for my own private use.
import UIKit
import AVFoundation
import MediaPlayer
class ViewController: UIViewController, AVAudioPlayerDelegate {
var testPlayer: AVAudioPlayer? = nil
func loadSound(filename: NSString) -> AVAudioPlayer {
let url = NSBundle.mainBundle().URLForResource(filename as String, withExtension: "caf")
var error: NSError? = nil
let player = AVAudioPlayer(contentsOfURL: url, error: &error)
if error != nil {
println("Error loading \(url): \(error?.localizedDescription)")
} else {
player.prepareToPlay()
}
return player
}
override func viewDidLoad() {
super.viewDidLoad()
self.testPlayer = self.loadSound("silence")
self.testPlayer?.numberOfLoops = -1
self.testPlayer?.play()
}
override func canBecomeFirstResponder() -> Bool {
return true
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
self.becomeFirstResponder()
UIApplication.sharedApplication().beginReceivingRemoteControlEvents()
}
override func remoteControlReceivedWithEvent(event: UIEvent) {
let rc = event.subtype
println("rc.rawValue: \(rc.rawValue)")
// take photo
}
}
I noticed that in Apple's camera app, the +/- volume buttons trigger the camera, and the microphone button pauses/plays any audio running in another app, but in this implementation the volume buttons still control the volume (and any audio has been paused when the app is launched).
An rc.rawValue: 103 corresponds to a single click of the microphone button, a double click returns 104, and a triple click returns 105, and then sometimes bumping a couple at a time returns a 108 or 109.
Based on Cody's answer but updated for 2019 (Swift 5)
import UIKit
import AVFoundation
import MediaPlayer
class ViewController: UIViewController, AVAudioPlayerDelegate {
var myPlayer: AVAudioPlayer? = nil
func loadSound(filename: NSString) -> AVAudioPlayer? {
let url = Bundle.main.url(forResource: filename as String, withExtension: "mp3")
do {
let player = try AVAudioPlayer(contentsOf: url ?? URL(fileURLWithPath: ""))
player.prepareToPlay()
return player
}
catch {
print("Error : \(error)")
return nil
}
}
override func viewDidLoad() {
super.viewDidLoad()
guard let testPlayer = loadSound(filename: "silence") else {
print("Not able to load the sound")
return
}
testPlayer.delegate = self
testPlayer.volume = 0.8
testPlayer.numberOfLoops = -1
myPlayer = testPlayer
myPlayer?.play()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.becomeFirstResponder()
UIApplication.shared.beginReceivingRemoteControlEvents()
}
override func remoteControlReceived(with event: UIEvent?) {
let rc = event?.subtype
print("rc.rawValue: \(rc?.rawValue)")
// Do your thing
}
}

Resources