I am using the MapKit to use CLLocationManagerDelegate to get the user's location. If I were to request for the user's location in ViewController's viewDidLoad() function, then the popup appears, asking the user for the user's input. Note: the two properties required to ask for the location (Location When In Use, and Location Always And When In Use) is added to Info.plist
That is,
import UIKit
import MapKit
class ViewController: UIViewController, CLLocationManagerDelegate {
var locationManager: CLLocationManager?
override func viewDidLoad() {
super.viewDidLoad()
locationManager = CLLocationManager()
self.locationManager?.desiredAccuracy = kCLLocationAccuracyBest
self.locationManager?.requestWhenInUseAuthorization()
self.locationManager?.delegate = self
self.locationManager?.startUpdatingLocation()
}
}
The code above works fine; when the program begins, it show a popup asking the user for their location.
However, If I was to create a new class MapController and put the same code inside that class, and create a new instance of MapController inside viewDidLoad(), then the popup immediately disappears when the program is run.
That is,
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let mapController = MapController(viewController: self)
mapController.initialise()
}
}
import MapKit
class MapController: NSObject, CLLocationManagerDelegate {
private let viewController: UIViewController
private var locationManager: CLLocationManager
required init(viewController: UIViewController) {
self.viewController = viewController
locationManager = CLLocationManager()
}
func initialise() {
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest
self.locationManager.requestWhenInUseAuthorization()
self.locationManager.delegate = self
self.locationManager.startUpdatingLocation()
}
}
When the code above is run, the popup asking for the user's location immediately disappears.
My question being: why does the popup stay when the locationManager code is in the viewDidLoad(), but when the code is separated into another class, and called into viewDidLoad(), it immediately disappears. Why does this happen?
How can I separate the locationManager code into another class without the popup immediately disappearing?
It's a memory management issue. In ViewController, you create a local variable named mapController in viewDidLoad. At the end of viewDidLoad, that MapController instance goes out of scope and gets deallocated.
Instead of using a local variable in viewDidLoad, create a property.
class ViewController: UIViewController {
var mapController: MapController!
override func viewDidLoad() {
super.viewDidLoad()
mapController = MapController(viewController: self)
mapController.initialise()
}
}
But this now creates a reference cycle since MapController is keeping a strong reference to the view controller.
So you also need to change the viewController property of MapController to be weak.
Related
I created an outlet in ViewController class and I'd like to modify it.
In the ViewController.swift file I have
import Cocoa
class ViewController: NSViewController {
#IBOutlet var LabelText: NSTextFieldCell?
override func viewDidLoad() {
super.viewDidLoad()
}
//other things
}
I'd like to change the background color of the label. How can I do that from AppDelegate?
At first I thought I could solve this problem using a function in ViewController and calling it in AppDelegate
func changeBackground() {
LabelText.textColor = NSColor.red
}
But soon I realised that it wasn't possible unless I used a static function. Then I tried to modify the code in ViewController like that
static func changeBackground() {
LabelText.textColor = NSColor.red
}
and call this function in AppDelegate like that
ViewController.changeBackground()
In this way I can access to changeBackground() function from AppDelegate, but in ViewController it gives me an error: Instance member 'LabelText' cannot be used on type 'ViewController'
I understood that this cannot be possible because somehow I'm calling "LabelText" before it's initialised (or something like that).
I don't know much about Swift and I'm trying to understand how it works. I've been searching for the answer to my question for hours, but still I don't know how to solve this.
Solution
As Rob suggested, the solution is to use NotificationCenter.
A useful link to understand how it works: https://www.appypie.com/notification-center-how-to-swift
Anyway, here how I modified the code.
In ViewController:
class ViewController: NSViewController {
#IBOutlet var label: NSTextFieldCell!
let didReceiveData = Notification.Name("didReceiveData")
override func viewDidLoad() {
NotificationCenter.default.addObserver(self, selector: #selector(onDidReceiveData(_:)), name: didReceiveData, object: nil)
super.viewDidLoad()
}
#objc func onDidReceiveData(_ notification: Notification) {
label.textColor = NSColor.red
}
}
And then, in AppDelegate:
let didReceiveData = Notification.Name("didReceiveData")
NotificationCenter.default.post(name: didReceiveData, object: nil)
I am new to Xcode and swift. I want to know that is there any way that we can customize the local notifications. If possible I also want to add a map on the notification.
Thanks in advance
You can add a target from File -> New -> Target -> Notification Content Extension
which will add an extension for your custom UI. Set the UNNotificationExtensionCategory to the string identifier.
If you want to add a map on the notification , add a Mapkit View into storyboard in the extension. I have attached the code for the map:
import UIKit
import UserNotifications
import UserNotificationsUI
import MapKit
import CoreLocation
class NotificationViewController: UIViewController, UNNotificationContentExtension{
// #IBOutlet weak var mapView: MKMapView!
#IBOutlet var label: UILabel?
fileprivate let locationManager:CLLocationManager = CLLocationManager()
override func viewDidLoad() {
super.viewDidLoad()
locationManager.requestWhenInUseAuthorization()
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.distanceFilter = kCLDistanceFilterNone
locationManager.startUpdatingLocation()
// mapView.showsUserLocation = true
}
func didReceive(_ notification: UNNotification) {
//self.label?.text = notification.request.content.body
self.label?.text = "Reminder for your charging "
}
#IBAction func notibutton(_ sender: Any) {
}
}
Thanks for the help.
I tried using the notification extension identifier and called in the function in the app delegate of the main target
It worked for me also the map is displayed in the notification.
I have been searching for a solution for two days but could not find anything on web.
I was working on a project and then instantly I was not able to declare anything new or iterate anything older that I wrote. I tried opening new project and start again but it was there again. You can see in the photo that it does need declaration for already declared variables such as cool and hype.
Now I am not able to work on any project.
import UIKit
class ViewController: UIViewController, CLLocationManagerDelegate {
var cool: Int = 2
var hype: Int = 2
hype = 2 + cool
override func viewDidLoad() {
super.viewDidLoad()
// Set up the location manager here.
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyHundredMeters
locationManager.requestWhenInUseAuthorization()
locationManager.startUpdatingLocation()
}
// ...
}
Use of undeclared type 'CLLocationManageDelegate'
This is because you haven't imported the CoreLocation module
Add the line import CoreLocation above the import UIKit
You can't perform the assignment of hype in that scope, try moving it into viewDidLoad
You must declare a location manager variable and initialize one:
Add the line var locationManager = CLLocationManager() above your other vars.
import CoreLocation
import UIKit
class ViewController: UIViewController, CLLocationManagerDelegate {
let locationManager = CLLocationManager()
var cool: Int = 2
var hype: Int = 2
override func viewDidLoad() {
super.viewDidLoad()
hype = 2 + cool
// Set up the location manager here.
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyHundredMeters
locationManager.requestWhenInUseAuthorization()
locationManager.startUpdatingLocation()
}
// ...
}
Actually, all your errors are totally generic, caused by syntax and other mistakes.
It should be CLLocationManager needs to be imported.
This produces errors in declaration and other places depending on it.
Variables can be declared but not manipulated outside a function on the top level of a class.
The variable locationManager has not been defined previously
I'm trying to create a MacOS app that plays audio or video files. I've followed the simple instructions on Apple's website here
But I want to use the File > Open menu items to bring up an NSOpenPanel, and pass that to the View Controller.
So presumably, the Open action should be in the AppDelegate, as the ViewController window might not be open.
And then pass the filename to a new instance of the ViewController window.
Is that right? If so, how do I "call" the View from AppDelegate?
Here's the AppDelegate:
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
#IBAction func browseFile(sender: AnyObject) {
let dialog = NSOpenPanel();
if (dialog.runModal() == NSModalResponseOK) {
let result = dialog.url // Pathname of the file
if (result != nil) {
// Pass the filepath to the window view thing.
} else {
// User clicked on "Cancel"
return
}
}
}
and here's the ViewController:
class ViewController: NSViewController {
#IBOutlet weak var playerView: AVPlayerView!
override func viewDidLoad() {
super.viewDidLoad()
// Get the URL somehow
let player = AVPlayer(url: url)
playerView.player = player
}
There are some details not disclosed in your question, but I believe I can provide the proper answer still.
You can call NSOpenPanel from AppDelegate, nothing wrong with that. Just note that user may cancel the dialog and how to handle that situation.
Considering the view the best thing is to create WindowController that is connected to the ViewController (it is like that by default) in the Storyboard, then access it from the code using NSStoryBoard.instantiateController(withIdentifier:), and then use its window property with something like window.makeKeyAndOrderFront(self) . If you have NSWindow or NSWindowController class in your code then you should initialize the class in the code and again make window key and front.
I have tabbarController where i put parent viewController with container view inside.
public override func viewDidLoad() {
viewControllers = [
ParentViewController()
]
}
On init i'm initializing 2 child view controllers and adding 1st controller (that does't contain MapView) as child viewController.
At some point of time i need to switch between child controllers, and in that point app crashes
public class ParentViewController: UIViewController {
#IBOutlet weak var containerView: UIView!
let firstChildController: ViewControllerWithoutMapView
let secondChildController: ViewControllerWithMapView
init() {
firstChildController = ViewControllerWithoutMapView()
secondChildController = ViewControllerWithMapView()
super.init(nibName: "ParentViewController", bundle: nil)
}
public override func viewDidLoad() {
firstChildController.view.frame = containerView.bounds
addChildViewController(firstChildController)
firstChildController.willMoveToParentViewController(nil)
containerView.addSubview(firstChildController.view)
firstChildController.didMoveToParentViewController(self)
}
func switchChildControllers() {
secondChildController.view.frame = containerView.bounds <<<<< crash here
.....
}
}
I know about crashes that appears if you're not importing MapKit, i tried to import it everywhere - no luck.
What is the correct way to switch child viewControllers with MapView inside one of it?