In my tvOS app I created a settings bundle, but I don't know how to get the values in TVML. I know how to do it in Obj-c or Swift.
var standardUserDefaults = NSUserDefaults.standardUserDefaults()
var us: AnyObject? = standardUserDefaults.objectForKey("your_preference")
if us==nil {
self.registerDefaultsFromSettingsBundle();
}
Any ideas about TVML way? Any kind of help is highly appreciated.
I am not aware of a direct TVJS access method... but you could easily set up a Swift/Obj-C-"proxy". Somewhat along those lines:
AppDelegate.swift
func appController(appController: TVApplicationController, evaluateAppJavaScriptInContext jsContext: JSContext) {
let jsInterface: cJsInterface = cJsInterface();
jsContext.setObject(jsInterface, forKeyedSubscript: "swiftInterface")
}
JsInterface.swift
#objc protocol jsInterfaceProtocol : JSExport {
func getSetting(setting: String) -> String
}
class cJsInterface: NSObject, jsInterfaceProtocol {
func getSetting(setting: String) -> String {
return "<yourSetting>"
}
}
on the JS side...
swiftInterface.getSetting(...)
Related
EDIT: (initial example was too simplified, so I rewrote the code to be more specific)
based on http://holko.pl/2016/01/05/typed-table-view-controller/
I am trying to see if it is possible to set the generic parameter of a type from a string..
Say we have this code
protocol Updatable
{
associatedtype ViewModel
func updateWith(viewModel: ViewModel)
}
class ToasterCell: UITableViewCell
{
var toast: String?
func updateWith(viewModel: String) {
toast = viewModel
//Additional config...
}
}
extension ToasterCell: Updatable
{
typealias ViewModel = String
}
class PriceCell: UITableViewCell
{
var tagPrice: Float?
func updateWith(viewModel: Float) {
tagPrice = viewModel
//Additional config
}
}
extension PriceCell: Updatable
{
typealias ViewModel = Float
}
protocol CellConfiguratorType {
var reuseIdentifier: String { get }
var cellClass: AnyClass { get }
func updateCell(_ cell: UITableViewCell)
}
class MyTypeTest<Cell> where Cell: Updatable , Cell: UITableViewCell
{
let viewModel: Cell.ViewModel
let reuseIdentifier: String = String(describing: Cell.self)
let cellClass: AnyClass = Cell.self
init(viewModel: Cell.ViewModel) {
self.viewModel = viewModel
}
func updateCell(_ cell: UITableViewCell)
{
if let c = cell as? Cell
{
c.updateWith(viewModel: viewModel)
}
}
}
extension MyTypeTest: CellConfiguratorType{
}
let myTT1 = MyTypeTest<PriceCell>(viewModel: 3.76)
let myTT2 = MyTypeTest<ToasterCell>(viewModel: "Carpe Diem")
let data = [myTT1, myTT2] as [CellConfiguratorType] // data for the tableView
//register Cell calss ...
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cellConf = data[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: cellConf.reuseIdentifier)
cellConf.updateCell(cell)
return cell
}
And we want to make it so that the type T is set from a string we get from a JSON response.
//Some JSON {"list":[{"k":"Price","v":368.0},"{"k":"ToasterCell","v":"YOLO"},"{"k":"Toaster","v":"Space"},{"k":"PriceCell","v":1999}]}
the JSON value does not map directly to any object/class, So I need to use that key "k" to know witch class to use.
I tried using the string from the "k" value to setup the cell configurator.
(short example)
//for now let skip any logic in decoding the value / viewModel.
let myTT1 = MyTypeTest<NSClassFromString(list.first.k + "Cell")>(viewModel: list.first.v as Any)
All I got was following errors:
Cannot assign value of type 'T' to type 'AnyClass' (aka 'AnyObject.Type')
Use of undeclared type 'myTypeOBJ'
Is there a way to do this via a string, or do I really need to create a huge "if-else" structure for whatever type I could get from my JSON responses?
EDIT:
I tried to add an init to the CellConfigurator with a param of type Cell so it can infer the Type from the param it self.
init(viewModel: Cell.ViewModel, inferUsing: Cell){....}
where I could attempt to use this (but is does not work as the PATs is getting in the way)
func getSafeBundelName() -> String
{
if let namespace = Bundle.main.infoDictionary!["CFBundleExecutable"] as? String
{
return namespace
}
return ""
}
let cellClass = NSClassFromString("\(getSafeBundelName()).PriceCell") as? UITableViewCell.Type
let cell = cellClass?.init()
let myTT1 = MyTypeTest(viewModel: list.first.v as Any, inferUsing: cell)
I get the error that the Cell type can not be infered. If I try to use the cellClass in the <>
ex: MyTypeTest<cellType>(viewModel: 3.76) all it gives me is that "cellClass" is not declared. Looks to me I am hitting a dead end where PATs become impossible to infer in any way that I can see. I find this limitation very very sad.
You can do this if you downcast the result from NSClassFromString to a type with a known initializer that you can call. If it will always be Toaster or a subclass then you can do:
if let myTypeOBJ = NSClassFromString("Toaster") as? Toaster.Type {
let test = myTypeTest(someOBJ: myTypeOBJ.init())
// test.someThing will be of type Toaster
}
In order for NSClassFromString to work you will also need to specify how you want the type name to be represented in objc, otherwise there will be some other stuff prepended to the type name:
#objc(Toaster)
class Toaster: NSObject
I'm a beginner at Swift so let me know if this doesn't quite make sense, but i have a JSON file that i can access in swift and parse into an array, from there i can get a string from the array and store it in a var. I want to be able to access this variable globally but i'm not sure how to do it.
With the help of another user "rmaddy". I have this code:
struct Games: Decodable {
let videoLink: String
}
class BroadService {
static let sharedInstance = BroadService()
func fetchBroadcasts(completion: #escaping ([Games]?) -> ()) {
let jsonUrlString = "LINK IS HERE."
guard let url = URL(string: jsonUrlString) else {
completion(nil)
return
}
URLSession.shared.dataTask(with: url) { (data, response, err) in
guard let data = data else {
completion(nil)
return
}
do {
let games = try JSONDecoder().decode([Games].self, from: data)
completion(games)
} catch let jsonErr {
print("Error deserializing json:", jsonErr)
completion(nil)
}
}.resume()
}
}
I can then access it in another class from here:
BroadService.sharedInstance.fetchBroadcasts { (games) in
if let games = games {
let game = games[indexPath]
let videoLink = game.videoLink
}
I want to be able to access the contents of "videoLink" globally, without having to use "BroadService.sharedInstance.fetchBroadcasts { (games) in" how would i go about doing this
You shouldn't use global variables, I don't think that's recommended in any language.
Now here you have what looks like a Singleton class (BroadService), that's good because it's a nice solution for what you're looking for.
Next all you need to do is add a property to that class. Let's say videoLink is a string, you can add a string property to BroadService, for example storedVideoLink as an optional String, and the next time you need to obtain that value after you have already fetched it, you can access it like so: BroadService.sharedInstance.storedVideoLink.
One more thing, to have BroadService work properly as a singleton, you should make its init private.
To sum up, here's what I'm suggesting:
class BroadService {
static let sharedInstance = BroadService()
var storedVideoLink: String?
private init() {} // to ensure only this class can init itself
func fetchBroadcasts(completion: #escaping ([Games]?) -> ()) {
// your code here
}
}
// somewhere else in your code:
BroadService.sharedInstance.fetchBroadcasts { (games) in
if let games = games {
let game = games[indexPath]
let videoLink = game.videoLink
BroadService.sharedInstance.storedVideoLink = videoLink
}
}
// now you can access it from anywhere as
// BroadService.sharedInstance.storedVideoLink
This way it all stays cohesive in the same class. You can even add a getter method for storedVideoLink so you don't have to access it directly, and in this method you could state that if the string is nil then you fetch the data, store the link to the string, and then return the string.
You could create a file with a struct called something like Global and create a static var and set that inside your completion block once you have fetched the games.
Here is an example.
struct Global {
static var games:[Any]? = nil
static func setGames(games:[Any]) {
Global.games = games
}
}
Then you fetch the data once upon load of the app or somewhere before you use the Global and set that property:
URLSession.shared.dataTask(with: url) { (data, response, err) in
guard let data = data else {
completion(nil)
return
}
do {
let games = try JSONDecoder().decode([Games].self, from: data)
Global.setGames(games: games)
completion(games)
} catch let jsonErr {
print("Error deserializing json:", jsonErr)
completion(nil)
}
}.resume()
Please note that this will make the Global.games accessible from everywhere but it will also not be a constant so you should be careful not to override it.
This way Global.games will be accessible from anywhere.
I am making an application which makes a lot of requests from an API. So I don't want to copy and past the code over and over. I was wondering how I can reuse my code in a some more efficient way? Maybe with extensions?
This is my code know:
func apiRequest() {
let config = URLSessionConfiguration.default
let username = "****"
let password = "****"
let loginString = String(format: "%#:%#", username, password)
let userPasswordData = loginString.data(using: String.Encoding.utf8)
let base64EncodedCredential = userPasswordData?.base64EncodedString()
let authString = "Basic " + (base64EncodedCredential)!
print(authString)
config.httpAdditionalHeaders = ["Authorization" : authString]
let session = URLSession(configuration: config)
var running = false
let urlProjects = NSURL(string: "https://start.jamespro.nl/v4/api/json/projects/?limit=10")
let task = session.dataTask(with: urlProjects! as URL) {
( data, response, error) in
if let taskHeader = response as? HTTPURLResponse {
print(taskHeader.statusCode)
}
if error != nil {
print("There is an error!!!")
print(error)
} else {
if let content = data {
do {
let dictionary = try JSONSerialization.jsonObject(with: content) as! [String:Any]
print(dictionary)
if let items = dictionary["items"] as? [[String:Any]] {
for item in items {
if let description = item["Description"] as? String {
self.projectNaam.append(description)
}
if let id = item["Id"] as? String {
self.projectId.append(id)
}
if let companyId = item["CompanyId"] as? String {
self.companyId.append(companyId)
}
}
}
self.apiRequestCompani()
}
catch {
print("Error: Could not get any data")
}
}
}
running = false
}
running = true
task.resume()
while running {
print("waiting...")
sleep(1)
}
}
Yes, you can use Extensions to create a BaseViewController and extend that where you want to use your code over and over again. Then you should abstract all dynamic data over input parameters to that method.
import UIKit
class BaseViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
}
func getApiRequest (Parameters) {
//API Request
}
And then in your view controller you just extend BaseViewController
class ViewController: BaseViewController {
override func viewDidLoad() {
super.viewDidLoad()
//Call method in baseviewcontroller
getApiRequest(parameters)
//Call method in self
self.getApiRequest(parameters)
}
override func getApiRequest(Parameters) {
//IF you need to override default configuration
}
So I don't want to copy and past the code over and over.
Absolutely right, no one aiming to get duplicated code; That's the issue of massive view controller. This issue appears since the view controller layer in your application handles most of the responsibilities, such as: getting data from the network, how data should be represented, deliver the formatted data to the view layer, etc...
There are many approaches for solving such an issue (using an appropriate architectural pattern for your application), for simplicity, I would recommend to apply the MVC-N (or MVCNetworking) approach into your app, it is almost the same usual MVC, with a separated files (managers), represent a new layer for handling -for instance- the integration with the external APIs.
Applying the MVN-N should not be that complex, nevertheless it needs to be described well (which might be too abroad to be descried in the answer), I would suggest to check the above mentioned apple example, also watching this video should be useful.
Right now I can inspect variables of an object using Mirror type. But can I set values for my variables using mirroring? Or maybe there's another pure-Swift way?
For example, I'd like to create an object (a Swift struct) from JSON. Is it possible without subclassing NSObject and using Objective-C functions for that?
This was the best I can do at the moment. It is still missing converting the mirrorObject back to its generic type. FYI this is using SwiftyJSON
func convertToObject<T>(json: JSON, genericObject: T) -> T {
let mirroredObject = Mirror(reflecting: genericObject)
for (_, var attr) in mirroredObject.children.enumerate() {
if let propertyName = attr.label as String! {
attr.value = json[propertyName]
print(propertyName)
print(attr.value)
}
}
// Figure out how to convert back to object type...
}
This is an old question, but the answer was not working for me.
I had to change my swift object to a NSObject to make things work, and also to have dynamic properties.
In my case I use the pod Marshal to deserialize Data.
class MyClass: NSObject, Unmarshaling
{
// #objc dynamic make property available for NSObject
#objc dynamic var myProperty: String?
required init(object: MarshaledObject) throws {
super.init()
initUsingReflection(object: object)
}
func initUsingReflection(object: MarshaledObject) {
let mirror = Mirror(reflecting: self)
// we go through children
for child in mirror.children {
guard let key = child.label else {
continue
}
// This line is here to get the value from json, in my case I already know the type I needed
let myValue: String = try! object.value(for: key)
// The trick is here, setValue only exist in NSObject and not in swift object.
self.setValue(myValue, forKey: key)
}
}
}
i'm trying to create a vary simple game on Swift based on Balloons.playground (that Apple showed on WWDC). There is code that Apple provides:
func fireCannon(cannon: SKNode) {
let balloon = createRandomBalloon()
displayBalloon(balloon, cannon)
fireBalloon(balloon, cannon)
}
let leftBalloonCannon = scene.childNodeWithName("//left_cannon")!
let left = SKAction.runBlock { fireCannon(leftBalloonCannon) }
I do the same (i suppose):
func displayFruit(xPos: CGFloat) -> Void {
let fruit = SKSpriteNode(imageNamed: "Banana")
fruit.size = CGSize(width: 100, height: 100)
fruit.position = CGPoint(x:xPos, y: 800)
fruit.physicsBody = SKPhysicsBody(texture: fruit.texture, size: fruit.size)
fruit.physicsBody?.affectedByGravity = true
self.addChild(fruit)
}
let start = SKAction.runBlock { displayFruit(100) }
This code above i put at override func didMoveToView(view: SKView)
I get an error: Cannot reference a local function with capture from another function
I know there was very the same questions, but solutions there don't seemed helpful for me.
What would be the right way to do this?
Thank you!
This is a local function:
override func didMoveToView(view: SKView) {
func displayFruit() {
// ...
}
}
This is not a local function:
override func didMoveToView(view: SKView) {
// ...
}
func displayFruit() {
// ...
}
Make it look like the second one.