I want the user to be able to know how many times they have visited each class. Then add together the totals from each page together to form a group sum. I want to print the total sum in the log file in each of the two view controllers. So just one string should be printed.
class oneV: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
UserDefaults.standard.set(true, forKey: "VC1")
}
}
class twoV: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
UserDefaults.standard.set(true, forKey: "VC2")
}
}
If you mean visited each view controller, when you say visited each class. Then i'd recommend you do it viewDidAppear.
class YourViewController: UIViewController {
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let key = String(describing: type(of: self))
let count = UserDefaults.standard.value(forKey: key) as? Int ?? 0
UserDefaults.standard.set(value + 1, forKey: key)
}
}
To make it simpler, you could use an extension on UIViewController.
extension UIViewController {
func updateVisitCount() {
let key = String(describing: type(of: self))
let count = UserDefaults.standard.value(forKey: key) as? Int ?? 0
UserDefaults.standard.set(count + 1, forKey: key)
}
}
Or, if you need this for every view controller that you create, then you can create a base view controller which you would use everywhere instead of UIViewController.
class BaseViewController: UIViewController {
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
updateVisitCount()
}
}
The most automatic solution would be inject the accounting call in viewDidLoad without replacing the original viewDidLoad.
Here demo purpose i've created a sample Playground
import UIKit
import PlaygroundSupport
extension UIViewController {
#objc dynamic func substitutedViewDidAppear() {
print("This is injected code in view did appear")
substitutedViewDidAppear() // it may look like recursive, but it isn't, actually it calls the original `viewDidAppear` method.
}
class func swizzle() {
let originalMethod = class_getInstanceMethod(UIViewController.self, #selector(viewDidAppear(_:)))
let substitutedMethod = class_getInstanceMethod(UIViewController.self, #selector(substitutedViewDidAppear))
if let originalMethod = originalMethod,
let substitutedMethod = substitutedMethod {
print("swizzled")
method_exchangeImplementations(originalMethod, substitutedMethod)
} else {
print("not swizzled")
}
}
}
class MyViewController : UIViewController {
override func loadView() {
let view = UIView()
view.backgroundColor = .white
let label = UILabel()
label.frame = CGRect(x: 150, y: 200, width: 200, height: 20)
label.text = "Hello World!"
label.textColor = .black
view.addSubview(label)
self.view = view
print("view loaded")
}
}
// Swizzle
UIViewController.swizzle() // call this in #top of didFinishLaunchingWithOptions
// Present the view controller in the Live View window
PlaygroundPage.current.liveView = MyViewController()
Output:
swizzled
view loaded
This is injected code in view did appear
Now in the substitutedViewDidAppear upper portion inject your counting code as #Rakesha Shastri Suggested, call the updateVisitCount method inside of substitutedViewDidAppear & place the UIViewController.swizzle() in applicationDidFinishLaunchingWithOptions before creating the root window.
Create a static variable. A static variable is a type of class, not object therefore throughout all objects a variable maybe maintained. I think this example may better explain how this works. Click here
In ViewDidLoad method call this function :
func updateVisitingCounter() {
var counter = UserDefaults.standard.integer(forKey: "firstPageCounter")
counter += 1
UserDefaults.standard.set(counter, forKey: "firstPageCounter")
}
You may set declare variables at project scope "outside of classes"
var vc1Count = 0
class oneV: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
vc1Count = vc1Count+1
}
}
var vc2Count = 0
class twoV: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
vc2Count = vc2Count+1
}
}
you can also declare these variables at a common place.
As per your requirements its kind of Analytics on app usage. You can implement in 2 ways
By storing data with screen visit in local DB and show it on Analysis Page or on summery page.
Sample code for storing Screen details in DB:
==> Create your Entity for Screen capture.
ScreenVisit.
==> Store Data with screen name.
let entity = NSEntityDescription.entity(forEntityName: "ScreenVisit", in: context)
let newVisit = NSManagedObject(entity: entity!, insertInto: context)
newVisit.setValue("HomeScreen", forKey: "screenname")
newVisit.setValue("1", forKey: "visited")
do {
try context.save()
} catch {
print("Failed saving")
}
==> Fetch data where you required.
let request = NSFetchRequest<NSFetchRequestResult>(entityName: "ScreenVisit")
//request.predicate = NSPredicate(format: <Your Filter Logic>)
request.returnsObjectsAsFaults = false
do {
let result = try context.fetch(request)
for data in result as! [NSManagedObject] {
print(data.value(forKey: "screenname") as! String)
print(data.value(forKey: "visited") as! String)
}
} catch {
print("Failed")
}
You can use any 3rd party library like Google analytics, Crashlytics for tracking your user actions.
Ref Links :
Firebase iOS analytics
Crashlytics
but as per my experience 2nd way is more convenient and powerful.
All depends on your requirements.
Hope this will helps you to get your user action captured.
Related
In createCardVC, I have used the carbonKit library to show tab bar. Initially, the array of data loaded using static data but now I am trying to use an array of data from webView javascript postMessage.
When createCardVC is loaded the carbonKit of the first tab is webViewVC will be loaded.
In webView, From postMessage it will list number menu items to show tab bar menu.
Here tabs values are dynamic and which will return from webView postMessage.
Here is the clear picture:
Here is the code createCardVC:
override public func viewDidLoad() {
super.viewDidLoad()
carbonTabSwipeNavigation = CarbonTabSwipeNavigation(items: ["Basic Details"], delegate: self)
carbonTabSwipeNavigation.insert(intoRootViewController: self, andTargetView: infoView)
carbonTabSwipeNavigation.toolbar.barTintColor = UIColor.white
carbonTabSwipeNavigation.setSelectedColor(UIColor.black)
carbonTabSwipeNavigation.setIndicatorColor(UIColor(hexString: "#363794"))
}//viewdidload
func onUserAction(data: String)
{
print("Data received: \(data)")
}
func sampleDelegateMethod(arg: Bool,completion: (Bool) -> ()){
completion(arg)
let singleTon = SingletonClass()
print(singleTon.sharedInstance.dataText)
}
#IBAction func backBtn(_ sender: Any) {
_ = navigationController?.popViewController(animated: true)
navigationController?.setNavigationBarHidden(false, animated: true)
tabBarController?.tabBar.isHidden = false
}
public init() {
super.init(nibName: "CreateCardViewController", bundle: Bundle(for: CreateCardViewController.self))
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
public func carbonTabSwipeNavigation(_ carbonTabSwipeNavigation: CarbonTabSwipeNavigation, viewControllerAt index: UInt) -> UIViewController {
return firstView()
}
func firstView() -> UIViewController {
let cont = WebViewController()
self.tabContView?.addChild(cont)
self.tabContView?.view.addSubview(cont.view)
cont.didMove(toParent: tabContView)
let authToken = UserDefaults.standard.string(forKey: "authToken")
cont.formKey = formKey
print("cont vl", formKey ?? "")
cont.processInstanceId = processInstanceId
cont.authTokenValue = authToken
cont.fullFormKey = fullFormKey
cont.taskIdValue = TaskIdValue
return cont
}
Here is the code for webView:
public func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
if message.name == "jsHandler" {
// print(message.body)
} else if message.name == "tabForm" {
print("dynamic tabs value::::",message.body)
let tabs = message.body
let jsonString = JSONStringify(value: tabs as AnyObject)
if let jsonData = jsonString.data(using: .utf8) {
do {
let decoder = JSONDecoder()
let mainObject = try decoder.decode(DynamicTabsModel.self, from: jsonData)
print("tab model:::", mainObject)
createCardVC?.onUserAction(data: "The quick brown fox jumps over the lazy dog")
delegate?.onPizzaReady(type: "Pizza di Mama")
createCardVC?.sampleDelegateMethod(arg: true, completion: { (success) -> Void in
print("Second line of code executed")
if success { // this will be equal to whatever value is set in this method call
createCardVC?.onUserAction(data: "The quick brown fox jumps over the lazy dog")
delegate?.onPizzaReady(type: "Pizza di Mama")
let singleTon = SingletonClass()
singleTon.sharedInstance.dataText = "store data"
print("delegate method ::: true")
} else {
print("delegate method ::: false")
}
})
print("called delegate")
} catch {
print(error.localizedDescription)
}
}
}
}
My question is:
How to return tab values from webView to CreateCardVC?
How to show dynamic tabs in carbonKit?
How to change dynamically ViewController for next tab using the same webViewController and url will return from webViewController.
Any help much appreciated pls...
Your approach is wrong for using CarbonKit. You have to provide list of tabs for CarbonKit while initialising it.
What you are trying to achieve is initialise CarbonKit with only one tab and then add more tabs as required, that is not supported by CarbonKit.
What you should do is get list of tabs before creating CarbonTabSwipeNavigation. If you don't have any option other than using WebView to get list of tabs, load your WebView first and get list of Tabs and then create CarbonTabSwipeNavigation.
You can try using "GLViewPagerController" it is more dynamic and API is similar to "UITableView"
Here is the link: GLViewPagerController
Other options:
Parchment
PolioPager
I strongly recommend to use Parchemnt. It uses Hashable.
https://github.com/rechsteiner/Parchment
See infinite datasource method for dynamic usage. Download the demo project and checkout Calendar Example.
https://github.com/rechsteiner/Parchment/blob/master/Documentation/infinite-data-source.md
I have an application written in the MVVM-C pattern, using RxSwift
After adding a new view programmatically, the application crashes with a
Thread 1: Fatal error: Unexpectedly found nil while unwrapping an
Optional value
error. I am at a complete loss, the implementation is almost exactly the same, minus the fact one view controller is a storyboard and one is not.
This is my new ViewController
import UIKit
import RxSwift
import RxCocoa
final class FeedViewController: TableViewController, ViewModelAttaching {
var viewModel: Attachable<FeedViewModel>!
var bindings: FeedViewModel.Bindings {
let viewWillAppear = rx.sentMessage(#selector(UIViewController.viewWillAppear(_:)))
.mapToVoid()
.asDriverOnErrorJustComplete()
let refresh = tableView.refreshControl!.rx
.controlEvent(.valueChanged)
.asDriver()
return FeedViewModel.Bindings(
fetchTrigger: Driver.merge(viewWillAppear, refresh),
selection: tableView.rx.itemSelected.asDriver()
)
}
override func viewDidLoad() {
super.viewDidLoad()
}
func bind(viewModel: FeedViewModel) -> FeedViewModel {
viewModel.posts
.drive(tableView.rx.items(cellIdentifier: FeedTableViewCell.reuseID, cellType: FeedTableViewCell.self)) { _, viewModel, cell in
cell.bind(to: viewModel)
}
.disposed(by: disposeBag)
viewModel.fetching
.drive(tableView.refreshControl!.rx.isRefreshing)
.disposed(by: disposeBag)
viewModel.errors
.delay(0.1)
.map { $0.localizedDescription }
.drive(errorAlert)
.disposed(by: disposeBag)
return viewModel
}
}
This is an existing one, that works but uses storyboards
final class PostsListViewController: TableViewController, ViewModelAttaching {
var viewModel: Attachable<PostsListViewModel>!
var bindings: PostsListViewModel.Bindings {
let viewWillAppear = rx.sentMessage(#selector(UIViewController.viewWillAppear(_:)))
.mapToVoid()
.asDriverOnErrorJustComplete()
let refresh = tableView.refreshControl!.rx
.controlEvent(.valueChanged)
.asDriver()
return PostsListViewModel.Bindings(
fetchTrigger: Driver.merge(viewWillAppear, refresh),
selection: tableView.rx.itemSelected.asDriver()
)
}
// MARK: - Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
setupView()
}
// MARK: - View Methods
private func setupView() {
title = "Posts"
}
func bind(viewModel: PostsListViewModel) -> PostsListViewModel {
viewModel.posts
.drive(tableView.rx.items(cellIdentifier: PostTableViewCell.reuseID, cellType: PostTableViewCell.self)) { _, viewModel, cell in
cell.bind(to: viewModel)
}
.disposed(by: disposeBag)
viewModel.fetching
.drive(tableView.refreshControl!.rx.isRefreshing)
.disposed(by: disposeBag)
viewModel.errors
.delay(0.1)
.map { $0.localizedDescription }
.drive(errorAlert)
.disposed(by: disposeBag)
return viewModel
}
}
They are basically exactly the same. The exception is thrown on the let refresh = tableView.refreshControl!.rx line.
The working coordinator, using a Storyboard is
import RxSwift
class PostsCoordinator: BaseCoordinator<Void> {
typealias Dependencies = HasPostService
private let navigationController: UINavigationController
private let dependencies: Dependencies
init(navigationController: UINavigationController, dependencies: Dependencies) {
self.navigationController = navigationController
self.dependencies = dependencies
}
override func start() -> Observable<Void> {
let viewController = PostsListViewController.instance()
navigationController.viewControllers = [viewController]
let avm: Attachable<PostsListViewModel> = .detached(dependencies)
let viewModel = viewController.attach(wrapper: avm)
viewModel.selectedPost
.drive(onNext: { [weak self] selection in
self?.showDetailView(with: selection)
})
.disposed(by: viewController.disposeBag)
// View will never be dismissed
return Observable.never()
}
private func showDetailView(with post: Post) {
let viewController = PostDetailViewController.instance()
viewController.viewModel = PostDetailViewModel(post: post)
navigationController.showDetailViewController(viewController, sender: nil)
}
}
I have an extension to allow me to instantiate it also
protocol Reusable {
static var reuseID: String { get }
}
extension Reusable {
static var reuseID: String {
return String(describing: self)
}
}
// MARK: - View Controller
extension UIViewController: Reusable {
class func instance() -> Self {
let storyboard = UIStoryboard(name: reuseID, bundle: nil)
return storyboard.instantiateViewController()
}
}
extension UIStoryboard {
func instantiateViewController<T: UIViewController>() -> T {
guard let viewController = self.instantiateViewController(withIdentifier: T.reuseID) as? T else {
fatalError("Unable to instantiate view controller: \(T.self)")
}
return viewController
}
}
The 'broken' coordinator is exactly the same, except I swapped
let viewController = PostsListViewController.instance()
for
let viewController = FeedViewController()
I am at a complete loss at to why this is throwing. Print statements and breakpoints at various points haven't turned up a nil on any values.
Please let me know if it would be easier to share a sample app as I appreciate the code snippets may not be the most obvious.
tableView.refreshControl is nil. You are trying to force access the nil refreshControl.
The Refreshing property is Enabled for the UITableViewController in your storyboard that works. In the programmatic version, the refreshControl is not created automatically.
The default value of the refreshControl property is nil. You need to instantiate and assign a UIRefreshControl to self.refreshControl before it exists.
When you create your view using a Storyboard and enable it, this is taken care of behind the scenes for you. Programmatically you will be required to implement this yourself.
I want this same statement repeated in all of my projects' viewDidLoad functions. I know I can just manually type it in but I am trying to find a way to increase my code speed. I don't know if I can use a extension file in this.
override func viewDidLoad() {
super.viewDidLoad()
let myswitchBoolValuefromFirstVc : Bool = UserDefaults.standard.bool(forKey: "mySwitch")// this is how you retrieve the bool value
// to see the value, just print those with conditions. you can use those for your things.
if myswitchBoolValuefromFirstVc == true {
print("true")
rosaryCounterLabel.isHidden = false
}
else {
print("false")
rosaryCounterLabel.isHidden = true
}
If you are prepared to abuse the Objective-C runtime that UIViewController still uses, you can use method swizzling to do what you ask. https://medium.com/#abhimuralidharan/method-swizzling-in-ios-swift-1f38edaf984f
let aClass: AnyClass! = object_getClass(instance)
let originalMethod = class_getInstanceMethod(aClass, #selector(viewDidLoad))
let swizzledMethod = class_getInstanceMethod(aClass, #selector(newViewDidLoad))
if let originalMethod = originalMethod, let swizzledMethod = swizzledMethod {
method_exchangeImplementations(originalMethod, swizzledMethod)
}
but I wouldn't recommend it. You trade between discoverability and repetition. In the case where you control all the code, it is going to be easier to maintain using a solution like subclassing that will still require some changes in every view controller.
Create a "master" view controller.
class MasterViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
//"Global" code here
}
}
And then inherit in all your other view controllers
class ViewController: MasterViewController{
override func viewDidLoad() {
super.viewDidLoad()
//controller specific code here
}
}
I want to pass data between two view controllers, but don't want the view to change when the users presses my save data button.
The users needs to fill in multiple data fields, and when finish can press another button to go to the second view controller.
I found many tutorials how to pass data using segue, but they all change view as soon as the 'save button is pressed'.
Any one can explain to me how to alter the code?
#Phillip Mills: here is how I used your code. (what am I doing wrong?)
code:
//////// declaring classes on FirstViewController (trying it first on only one ViewController)
class FakeVC1 {
func userInput() {
DataModel.shared.username = outbj14u.text
}
class FakeVC2 {
func viewAppears() {
if let name = DataModel.shared.username {
outbj14p.text = name
print("I have nothing to say")
}
}
}
class DataModel {
static let shared = DataModel()
var username: String?
}
////till here
//// here is where i call the functions
override func viewDidAppear(_ animated: Bool) {
FakeVC1().userInput()
FakeVC2().viewAppears()
if let xbj14p = UserDefaults.standard.object(forKey: "outbj14p") as? String
{
outbj14p.text = xbj14p
}
if let xbj14u = UserDefaults.standard.object(forKey: "outbj14u") as? String
{
outbj14u.text = xbj14u
}
////
#Phillip Mills: Below is what I have know. I think I got the code on the FirstViewController right, but the code on the Second View controller must be wrong. I don't get any errors, but the text field on the SecondViewController remains unchanged after putting input on in the FirstViewController
//// Code on the FirstViewController
class DataModel {
static let shared = DataModel()
var username: String?
}
#IBAction func savebj14p(_ sender: Any) {
outbj14p.text = inbj14p.text
DataModel.shared.username = outbj14p.text
UserDefaults.standard.set(inbj14p.text, forKey: "namebj14p")
}
//and on the SecondViewController
#IBOutlet weak var bj14u: UILabel! // connected to a label
//and
class DataModel {
static let shared = DataModel()
var username: String?
}
override func viewDidAppear(_ animated: Bool) {
if let name = DataModel.shared.username {
bj14u.text = name
}
}
In your case, don't pass data.
Create a shared object to act as your data model. When users fill in the fields, update the data model.
When the user moves to the second controller/view, that controller uses the data model object to show what it needs to.
class FakeVC1 {
func userInput() {
DataModel.shared.username = "Me"
}
}
class FakeVC2 {
func viewAppears() {
if let name = DataModel.shared.username {
print(name)
} else {
print("I have nothing to say")
}
}
}
class DataModel {
static let shared = DataModel()
var username: String?
}
FakeVC1().userInput()
FakeVC2().viewAppears()
If you need to pass value to another viewcontroller without changing the view , you can user NSNotificationCenter class
Refer this link for more details
NSNotificationCenter addObserver in Swift
what i will recommend is to use a global variable or array, you will have the info in all view controllers and you will be able to call it in your new view controller.
I have developed a game in Xcode using sprite kit, and scenes. Now I am trying to integrate the functionality to post high scores to twitter and Facebook. I've looked around, and most people say to use SLComposeServiceViewController which is fine, until I try and present it. Because my app really only uses scenes, they never have the member function "presentViewController(....)". Thus, I am unable to ever present it. Anyone know any way around this?
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch:UITouch = touches.first!
let touchLocation = touch.location(in: self)
let touchedNode = self.atPoint(touchLocation)
if (touchedNode.name == "tryAgain") {
let nextScene = Scene_LiveGame(size: self.scene!.size)
nextScene.scaleMode = self.scaleMode
self.view?.presentScene(nextScene, transition: SKTransition.fade(withDuration: 0.5))
}
else if (touchedNode.name == "share") {
if SLComposeViewController.isAvailable(forServiceType: SLServiceTypeFacebook) {
let fShare = SLComposeViewController(forServiceType: SLServiceTypeFacebook)
self.presentViewController(fShare!, animated: true, completion: nil)
//^This is where my problem is. Xcode is telling me that self has no member function presentViewController which I totally understand, because its a scene and thus doesn't share those functions. But every resource online has shown me this is the only way to do it
}
}
Your are getting this error because you need to present a UIViewController from another UIViewController. So
self.presentViewController(...)
will not work because self (SKScene) is not a UIViewController. To present from a SKScene you would have to say this
view?.window?.rootViewController?.presentViewController(fShare!, animated: true, completion: nil)
I would recommend that you do not use those APIs anymore. Its better to use a UIActivityViewController for your sharing needs. This way you only need one share button in your app and you can share to all sorts of services (email, Twitter, Facebook, iMessage, WhatsApp etc).
Create a new Swift file and add this code.
enum ShareMenu {
static func open(text: String, image: UIImage?, appStoreURL: String?, from viewController: UIViewController?) {
guard let viewController = viewController, let view = viewController.view else { return }
// Activity items
var activityItems = [Any]()
// Text
activityItems.append(text)
// Image
if let image = image {
activityItems.append(image)
}
/// App url
if let appStoreURL = appStoreURL {
let items = ActivityControllerItems(appStoreURL: appStoreURL)
activityItems.append(items)
}
// Activity controller
let activityController = UIActivityViewController(activityItems: activityItems, applicationActivities: nil)
// iPad settings
if UIDevice.current.userInterfaceIdiom == .pad {
activityController.modalPresentationStyle = .popover
activityController.popoverPresentationController?.sourceView = view
activityController.popoverPresentationController?.sourceRect = CGRect(x: view.bounds.midX, y: view.bounds.midY, width: 0, height: 0)
activityController.popoverPresentationController?.permittedArrowDirections = UIPopoverArrowDirection.init(rawValue: 0)
}
// Excluded activity types
activityController.excludedActivityTypes = [
.airDrop,
.print,
.assignToContact,
.addToReadingList,
]
// Present
DispatchQueue.main.async {
viewController.present(activityController, animated: true)
}
// Completion handler
activityController.completionWithItemsHandler = { (activity, success, items, error) in
guard success else {
if let error = error {
print(error.localizedDescription)
}
return
}
// do something if needed
}
}
}
// MARK: - Activity Controller Items
/**
ActivityControllerItems
*/
private final class ActivityControllerItems: NSObject {
// MARK: - Properties
/// App name
fileprivate let appName = Bundle.main.infoDictionary?["CFBundleName"] as? String ?? "-"
/// App store web url
fileprivate let appStoreURL: String
// MARK: - Init
/// Init
fileprivate init(appStoreURL: String) {
self.appStoreURL = appStoreURL
super.init()
}
}
// MARK: - UIActivityItemSource
/// UIActivityItemSource
extension ActivityControllerItems: UIActivityItemSource {
/// Getting data items
/// Placeholder item
func activityViewControllerPlaceholderItem(_ activityViewController: UIActivityViewController) -> Any {
return ""
}
/// Item for actity type
func activityViewController(_ activityViewController: UIActivityViewController, itemForActivityType activityType: UIActivityType) -> Any? {
return URL(string: appStoreURL) ?? appName
}
/// Provide info about data items
/// Subject field for services such as email
func activityViewController(_ activityViewController: UIActivityViewController, subjectForActivityType activityType: UIActivityType?) -> String {
return appName
}
}
Than when the share button is pressed you can call it like so
ShareMenu.open(
text: "Can you beat my score?",
image: UIImage(...), // set to nil if unused
appStoreURL: "your iTunes app store URL", // set to nil if unused
from: view?.window?.rootViewController
)
Bear in mind that the image and appStoreURL will not show up everywhere, it depends on the sharing service.
You can also use your score value from your scene and add it to the text e.g
ShareMenu.open(
text: "Can you beat my score \(self.score)?",
...
)
Hope this helps
I will not go into SLComposeViewController related code. I will just show you two techniques aside from what crashoverride777 proposed. So the first technique would be using notifications, like this:
GameScene:
import SpriteKit
let kNotificationName = "myNotificationName"
class GameScene: SKScene {
private func postNotification(named name:String){
NotificationCenter.default.post(
Notification(name: Notification.Name(rawValue: name),
object: self,
userInfo: ["key":"value"]))
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
self.postNotification(named: kNotificationName)
}
}
Here, you post a notification by tapping the screen. A desired view controller class can listen for this notification, like this:
import UIKit
import SpriteKit
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(
self,
selector: #selector(self.handle(notification:)),
name: NSNotification.Name(rawValue: kNotificationName),
object: nil)
if let view = self.view as! SKView? {
// Load the SKScene from 'GameScene.sks'
if let scene = GameScene(fileNamed: "GameScene") {
// Set the scale mode to scale to fit the window
scene.scaleMode = .aspectFill
// Present the scene
view.presentScene(scene)
}
}
}
func handle(notification:Notification){
print("Notification : \(notification)")
}
}
Here, we add self as an observer for this notification - means that when notification happens, an appropriate handling method will be called (and that is our custom handle(notification:) method. In that method, you should call your code:
if SLComposeViewController.isAvailable(forServiceType: SLServiceTypeFacebook) {
let fShare = SLComposeViewController(forServiceType: SLServiceTypeFacebook)
self.presentViewController(fShare!, animated: true, completion: nil)
}
Actually, I will write another example for delegation, to keep things clean :)
As I said, this can be done using notifications, like in this answer, or you can go with delegation:
First you should declare the MyDelegate protocol which defines one method called myMethod().
protocol MyDelegate:class {
func myMethod()
}
That method is a requirement that every class must implement if it conforms to this protocl.
In our example, you can look at the scene as a worker, and a view controller as a boss. When the scene finishes its task, it notifies its boss (delegates responsibilities to him) about job completion, so that boss can decide what is next. I mean, I could say : "The scene is a boss, and it delegates responsibilities to his employee, the view controller..." But it doesn't really matter who you consider as a boss... The delegation pattern matters.
So, the view controller, should conform to this protocol, and it will implement the myMethod() (that will be called by the scene later):
class GameViewController: UIViewController, MyDelegate {
override func viewDidLoad() {
super.viewDidLoad()
//MARK: Conforming to MyDelegate protocol
if let view = self.view as! SKView? {
// Load the SKScene from 'GameScene.sks'
if let scene = GameScene(fileNamed: "GameScene") {
// Set the scale mode to scale to fit the window
scene.scaleMode = .aspectFill
scene.myDelegate = self
// Present the scene
view.presentScene(scene)
}
}
}
func myMethod(){
print("Do your stuff here")
}
}
And here is a code from the GameScene where you define the myDelegate property that we use to communicate with our view controller:
import SpriteKit
class GameScene: SKScene {
weak var myDelegate:MyDelegate?
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
self.myDelegate?.myMethod()
}
}
To find out when to choose delegation over notifications and vice-versa take a look at this article (or just search SO, there are some nice posts about that).