I'm looking to have a class or struct (does not matter) with properties, where everything may only be initialized once in the app. Any attempt to modify the class or its properties will fail or not be possible.
Is this possible?
I've come up with this so far:
public struct ScreenInfo
{
static var scaleFactor:Int = 0
public init(initialScaleFactor:Int)
{
if (ScreenInfo.scaleFactor == 0) {
ScreenInfo.scaleFactor = initialScaleFactor
}
}
public static func getScaleFactor()
-> Int
{
return scaleFactor
}
}
let si1:ScreenInfo = ScreenInfo(initialScaleFactor:11)
ScreenInfo.getScaleFactor() // = 11
let si2:ScreenInfo = ScreenInfo(initialScaleFactor:22)
ScreenInfo.getScaleFactor() // = 11
What you want is somewhat unusual, but it is possible.
public struct ScreenInfo {
private static var _scaleFactor: Int?
public static var scaleFactor: Int? {
set {
if _scaleFactor == nil {
_scaleFactor = newValue
} else {
// Optionally throw an exception or something
}
}
get {
return _scaleFactor
}
}
}
ScreenInfo.scaleFactor // nil
ScreenInfo.scaleFactor = 5
ScreenInfo.scaleFactor // 5
ScreenInfo.scaleFactor = 15
ScreenInfo.scaleFactor // 5
Related
I'm using this maps drawer library which is written in UIKit in a SwiftUI project. I have a SwiftUI ListView that I'm using in the project via a UIHostingController but I want to disable scrolling when the drawers position is not open but I'm not sure how to pass drawer position data to the ListView in order to disable it.
#objc public class PulleyPosition: NSObject {
public static let collapsed = PulleyPosition(rawValue: 0)
public static let partiallyRevealed = PulleyPosition(rawValue: 1)
public static let open = PulleyPosition(rawValue: 2)
public static let closed = PulleyPosition(rawValue: 3)
public static let all: [PulleyPosition] = [
.collapsed,
.partiallyRevealed,
.open,
.closed
]
public static let compact: [PulleyPosition] = [
.collapsed,
.open,
.closed
]
public let rawValue: Int
public init(rawValue: Int) {
if rawValue < 0 || rawValue > 3 {
print("PulleyViewController: A raw value of \(rawValue) is not supported. You have to use one of the predefined values in PulleyPosition. Defaulting to `collapsed`.")
self.rawValue = 0
} else {
self.rawValue = rawValue
}
}
/// Return one of the defined positions for the given string.
///
/// - Parameter string: The string, preferably obtained by `stringFor(position:)`
/// - Returns: The `PulleyPosition` or `.collapsed` if the string didn't match.
public static func positionFor(string: String?) -> PulleyPosition {
guard let positionString = string?.lowercased() else {
return .collapsed
}
switch positionString {
case "collapsed":
return .collapsed
case "partiallyrevealed":
return .partiallyRevealed
case "open":
return .open
case "closed":
return .closed
default:
print("PulleyViewController: Position for string '\(positionString)' not found. Available values are: collapsed, partiallyRevealed, open, and closed. Defaulting to collapsed.")
return .collapsed
}
}
public override func isEqual(_ object: Any?) -> Bool {
guard let position = object as? PulleyPosition else {
return false
}
return self.rawValue == position.rawValue
}
public override var description: String {
switch rawValue {
case 0:
return "collapsed"
case 1:
return "partiallyrevealed"
case 2:
return "open"
case 3:
return "closed"
default:
return "collapsed"
}
}
}
inside PulleyViewController: UIViewController
(maybe pass the drawerPosition through a UIViewRepresentable Coordinator?)
public fileprivate(set) var drawerPosition: PulleyPosition = .collapsed {
didSet {
setNeedsStatusBarAppearanceUpdate()
}
}
SwiftUI ListView
import SwiftUI
//class ListViewDelegate: ObservableObject {
// var pulleyPosition = PulleyPosition
//
struct ListView: View {
#EnvironmentObject var drawerPositionModel: DrawerPositionVM
var body: some View {
ScrollViewReader { proxy in
VStack {
Button("Jump to #50") {
proxy.scrollTo(50)
}
List(0..<100, id: \.self) { i in
Text("Example \(i)")
.id(i)
}.scrollDisabled(drawerPositionModel == .partiallyRevealed ? true : false)
}
}
}
}
class ListViewVHC: UIHostingController<ListView> {
required init?(coder: NSCoder) {
super.init (coder: coder, rootView: ListView())
}
}
struct ListView_Previews: PreviewProvider {
static var previews: some View {
ListView()
}
}
Trying hard to code in Swift 5 the Java example below.
Generally, I want to have an Observable protocol which will be adopted by multiple other protocols. I need these protocols to be types in functions' arguments, so that these functions can add additional observers.
In Java, it is very easy to do. The code prints out:
Observer 1 changed to 10
Observer 2 changed to 10
,
interface Observable<O> {
void addObserver(O observer);
}
interface Settings extends Observable<SettingsObserver> {
void setInterval(int interval);
}
interface SettingsObserver {
void intervalChanged(int interval);
}
class AppSettings implements Settings {
private List<SettingsObserver> observers = new ArrayList<>();
#Override public void addObserver(SettingsObserver observer) { observers.add(observer); }
#Override public void setInterval(int interval) { observers.forEach(observer -> observer.intervalChanged(interval)); }
}
class Observer1 implements SettingsObserver {
#Override public void intervalChanged(int interval) {
System.out.println("Observer 1 changed to " + interval);
}
}
class Observer2 implements SettingsObserver {
#Override public void intervalChanged(int interval) {
System.out.println("Observer 2 changed to " + interval);
}
}
class Main {
public static void main(String[] args) {
Observer1 observer1 = new Observer1();
Settings settings = new AppSettings();
settings.addObserver(observer1);
Main main = new Main();
main.run(settings);
}
void run(Settings settings) {
Observer2 observer2 = new Observer2();
settings.addObserver(observer2);
settings.setInterval(10);
}
}
While it's simple to create a generic wrapper to which you can add your own observables, there are two native solutions that you should use instead.
Notifications.
When value is changed, send a notification using NotificationCenter.default. Observers should listen to these notifications. Notification are a crucial part of the ecosystem:
class AppSettings {
enum Notifications {
static let intervalChanged = Notification.Name("AppSettingsIntervalChangedNotification")
}
var interval: TimeInterval = 0 {
didSet {
NotificationCenter.default.post(name: Notifications.intervalChanged, object: self)
}
}
}
let settings = AppSettings()
let observer = NotificationCenter.default.addObserver(
forName: AppSettings.Notifications.intervalChanged,
object: settings,
queue: nil
) { [weak settings] _ in
guard let settings = settings else { return }
print(settings.interval)
}
settings.interval = 10
Key-value observing (KVO)
If you inherit your objects from NSObject, you can simply add a direct observer to any Obj-C compatible value:
class AppSettings: NSObject {
#objc dynamic var interval: TimeInterval = 0
}
let settings = AppSettings()
let observer: NSKeyValueObservation = settings.observe(\.interval, options: .new) { _, change in
print(change.newValue)
}
settings.interval = 10
See https://developer.apple.com/documentation/swift/cocoa_design_patterns/using_key-value_observing_in_swift
Just for completeness a simple generic observer here:
class Observable<ValueType> {
typealias Observer = (ValueType) -> Void
var observers: [Observer] = []
var value: ValueType {
didSet {
for observer in observers {
observer(value)
}
}
}
init(_ defaultValue: ValueType) {
value = defaultValue
}
func addObserver(_ observer: #escaping Observer) {
observers.append(observer)
}
}
class AppSettings {
let interval: Observable<TimeInterval> = Observable(0)
}
let settings = AppSettings()
settings.interval.addObserver { interval in
print(interval)
}
settings.interval.value = 10
Note that all my observers are simple closures. The reason why Java uses objects as observers is mostly historical due to Java limitations. There is no need for Observable or Observer protocols in Swift.
Depending on your needs, you may be able to get by with property observers in Swift. It allows you to take action when a property is going to be changed or has changed. It is also less complicated than a full observerable type.
Here is Apple's example from the Swift manual:
class StepCounter {
var totalSteps: Int = 0 {
willSet(newTotalSteps) {
print("About to set totalSteps to \(newTotalSteps)")
}
didSet {
if totalSteps > oldValue {
print("Added \(totalSteps - oldValue) steps")
}
}
}
}
let stepCounter = StepCounter()
stepCounter.totalSteps = 200
// About to set totalSteps to 200
// Added 200 steps
stepCounter.totalSteps = 360
// About to set totalSteps to 360
// Added 160 steps
stepCounter.totalSteps = 896
// About to set totalSteps to 896
// Added 536 steps
You would want to use the didSet() function. You could also call another function within the observer.
You could also use the property observers to write a simple observable-like class if you do not want to use a framework such as RxSwift or Apple's new Combine.
Here is a simple example that just uses closures instead of classes:
class ClassToWatch {
typealias ObservingFunc = (ClassToWatch) -> Void
private var observers: [ObservingFunc] = []
func addObserver(_ closure: #escaping ObservingFunc) {
observers.append(closure)
}
private func valueChanged() {
observers.forEach { observer in
observer(self)
}
}
var value1: Int = 0 {
didSet {
valueChanged()
}
}
var value2: String = "" {
didSet {
valueChanged()
}
}
}
var myclass = ClassToWatch()
myclass.addObserver { object in
print("Observer 1: \(object.value1) \(object.value2)")
}
myclass.addObserver { object in
print("Observer 2: \(object.value1) \(object.value2)")
}
myclass.value1 = 3
myclass.value2 = "Test"
Your Java code could be directly translated into Swift code. Here is my translation, with some degree of "Swiftification":
protocol Observable {
associatedtype ObserverType
func addObserver(_ observer: ObserverType)
}
protocol Settings : Observable where ObserverType == SettingsObserver {
var interval: Int { get set }
}
protocol SettingsObserver {
func intervalDidChange(newValue: Int)
}
class Observer1 : SettingsObserver {
func intervalDidChange(newValue: Int) {
print("Observer 1 changed to \(newValue)")
}
}
class Observer2 : SettingsObserver {
func intervalDidChange(newValue: Int) {
print("Observer 2 changed to \(newValue)")
}
}
class AppSettings: Settings {
var interval: Int = 0 {
didSet {
observers.forEach { $0.intervalDidChange(newValue: interval) }
}
}
private var observers: [SettingsObserver] = []
func addObserver(_ observer: SettingsObserver) {
observers.append(observer)
}
}
let settings = AppSettings()
settings.addObserver(Observer1())
settings.addObserver(Observer2())
settings.interval = 10
Although Observable cannot be used as a parameter type, the protocols that derive from it that also specifies the associated type, can.
You could go one step further and make SettingsObserver a typealias of (Int) -> Void. This way you don't need all those different ObserverX classes.
typelias SettingsObserver = (Int) -> Void
The addObserver calls would then become:
settings.addObserver { print("Observer 1 changed to \($0)") }
settings.addObserver { print("Observer 2 changed to \($0)") }
And the call in didSet would change to:
observers.forEach { $0(interval) }
Also, I don't understand why Settings exist. Can't you just conform AppSettings directly to Observable? I mean, I know the idea of program to interface and all that, but IMO this is a bit too much...
I tried to do this so get my settings saved whenever the App moves to the background or gets killed or whatever.
I want to access and set the property "useLimits" all over my App.
Why is it not working?
Is there a better more elegant way to achieve this?
import UIKit
class Settings: NSObject
{
static let sharedInstance = Settings()
private let kUseLimits = "kUseLimits"
var useLimits = false
override init()
{
super.init()
NSNotificationCenter.defaultCenter().addObserver(
self,
selector: #selector(Settings.save),
name: UIApplicationWillResignActiveNotification,
object: nil)
let userdefaults = NSUserDefaults.standardUserDefaults()
self.useLimits = userdefaults.boolForKey(kUseLimits)
}
deinit
{
NSNotificationCenter.defaultCenter().removeObserver(self)
save()
}
func save()
{
let userdefaults = NSUserDefaults.standardUserDefaults()
userdefaults.setBool(self.useLimits, forKey: kUseLimits)
userdefaults.synchronize()
}
func reset()
{
self.useLimits = false
save()
}
}
I think something like this will be good:
class AppSettings {
private struct Keys {
static let useLimits = "AppSttings.useLimits"
}
static var useLimits: Bool {
set {
NSUserDefaults.standardUserDefaults().setBool(newValue, forKey: Keys.useLimits)
}
get {
return NSUserDefaults.standardUserDefaults().boolForKey(Keys.useLimits)
}
}
static func rest() {
useLimits = false
}
}
P.S. Starting from iOS 8 you don't need to call synchronize() in NSUserDefault
P.S.S. NSUserDefaults.standardUserDefaults().boolForKey(Keys.useLimits) will return false if there not such object, if you need specific default value please check on object or use NSUserDefaults.standardUserDefaults().registerDefaults()
P.S.S.S. It wont effect your performance much, so you can read from UD and write there just on on the run, but if you want too performance code, you can do something like this:
private static var _useLimits: Bool?
static var useLimits: Bool {
set {
NSUserDefaults.standardUserDefaults().setBool(newValue, forKey: Keys.useLimits)
_useLimits = newValue
}
get {
if _useLimits == nil {
_useLimits = NSUserDefaults.standardUserDefaults().boolForKey(Keys.useLimits)
}
return _useLimits!
}
}
or more elegant for current value:
private static var _useLimits: Bool = NSUserDefaults.standardUserDefaults().boolForKey(Keys.useLimits)
static var useLimits: Bool {
set {
NSUserDefaults.standardUserDefaults().setBool(newValue, forKey: Keys.useLimits)
_useLimits = newValue
}
get {
return _useLimits
}
}
So I have the following superclass:
class Vehicle {
private var _maxSpeed: Int = 100
var maxSpeed: Int {
get {
return _maxSpeed
}
var tooFast: Bool {
get {
if maxSpeed >= 140 {
return false
} else {
return true
}
}
}
}
Plus I have some subclasses in which I want to override maxSpeed... per example:
class SuperCar: Vehicle {
//override the maxspeed...
}
But how should I approach this? Or is this only possible if we don't make it private? I tried to throw the private part out of the window but that won't work as well...
class Vehicle {
var maxSpeed: Int = 100
var tooFast: Bool {
get {
if maxSpeed >= 140 {
return false
} else {
return true
}
}
}
}
class SuperCar: Vehicle {
// override the maxSpeed...
override var maxSpeed: Int = 200
// Will not work...
}
Just put the class and subclass in the same file. private has nothing to do with inheritance. It has to do with file scope. Anything in the same file has access to private members.
That said, you almost certainly shouldn't be using inheritance here. Vehicle should almost certainly be a protocol. Then you wouldn't have any of the headaches of inheritance or private.
protocol Vehicle {
var maxSpeed: Int {get}
}
extension Vehicle {
// Default implementation if none is given
var maxSpeed: Int { return 100 }
// Another method that applies to all Vehicles
var tooFast: Bool {
return maxSpeed < 140 // (feels like this should be >= 140, but matching your code)
}
}
struct SuperCar: Vehicle {
// override the default implementation for the protcocol
var maxSpeed = 200
}
Set your private member variables in the init method
class Vehicle{
private var maxSpeed: Int
init(maxSpeed: Int = 100){
self.maxSpeed = maxSpeed
}
}
class SuperCar: Vehicle {
override init(maxSpeed: Int = 200){
super.init(maxSpeed: maxSpeed)
}
}
For a Swift 3 version of Rob's answer, you can't override private, but interestingly you can with fileprivate computed properties. You have to put the class in the same file though.
I did this today:
protocol Renderer {
}
class BaseClass {
private let _renderer: Renderer = BaseClassRenderer()
fileprivate var renderer: Renderer {
return _renderer
}
}
class Subclass: BaseClass {
private let _renderer: Renderer = SubclassRenderer()
override fileprivate var renderer: Renderer {
return _renderer
}
}
you can override computed property
class C {
var a: Int { return 10 }
}
class D:C {
override var a: Int { return 100 }
I have created a singleton for my iOS application to access certain things globally. When I launch the application int the simulator or on my iPhone/iPad however, it sticks on the launch screen, and never reaches the appdelegates didFinishLaunchingWithOptions method (I tried to println in it). If I remove the singleton and just leave the methods and variables as global it works perfectly. Which leaves me to believe it's the singleton causing this crash. Here's the code I use. If I comment out the lines currently commented out it works perfectly like this "gameName" anywhere in my code but I know this isn't great practice so if I uncomment them and access the singleton like this "Global.sharedInstance.gameName" is when the app does not launch. I do call this singleton many times throughout the app so I'm not sure if that's the issue.
//class Global {
//
// static let sharedInstance = Global()
//
// private init() {
// println("Global Singleton created");
// }
private var optionsModel = OptionsModel()
private var gamesModel = GamesModel()
private var savesModel = SavesModel()
var device = (UIApplication.sharedApplication().delegate as! AppDelegate).device
var screenWidth = (UIApplication.sharedApplication().delegate as! AppDelegate).window!.bounds.width
var screenHeight = (UIApplication.sharedApplication().delegate as! AppDelegate).window!.bounds.height
var context = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext!
var PI = CGFloat(M_PI)
var gameIndex = 0
var player: AudioPlayer!
var gameLevel = ""
func loadSong(name: String, loops: Int) {
player = AudioPlayer(name: name, loopCount: loops)
}
func playAduio(fileName: String) {
loadSong("\(fileName)Audio", 0)
player!.play()
}
var gameName: String {
get {
return gamesModel.getName(gameIndex)
}
}
var gameDescription: String {
get {
return gamesModel.getDescription(gameIndex)
}
}
var gameIntervals: NSTimeInterval {
get {
return gamesModel.getIntervals(gameIndex)
}
}
var gameNeedsMic: Bool {
get {
return gamesModel.getMic(gameIndex)
}
}
var gameNeedsSpeech: Bool {
get {
return gamesModel.getSpeech(gameIndex)
}
}
var appLocked: Bool {
get {
return optionsModel.appLocked
}
set {
optionsModel.appLocked = newValue
}
}
var supervisorLoggedIn: Bool {
get {
return optionsModel.supervisorLoggedIn
}
set {
optionsModel.supervisorLoggedIn = newValue
}
}
var themeSongMuted: Bool {
get {
return optionsModel.themeSongMuted
}
set {
optionsModel.themeSongMuted = newValue
}
}
var gameCount: Int {
get {
return optionsModel.gameCount
}
set {
optionsModel.gameCount = newValue
}
}
var gameHighscore: Int {
get {
return savesModel.getHighscore(gameIndex)
}
set {
savesModel.setHighscore(newValue)
}
}
var gameStarCount: Int {
get {
return savesModel.getStarCount(gameIndex)
}
set {
savesModel.setStarCount(newValue)
}
}
//}
Silly me. I fixed it.
The problem was that (this is probably obvious to some of you but singletons are new to me) the singleton loaded before the app delegate loaded and some of the variables in the singleton tried to get information from the app delegate before it was created so it caused this crash. Changed it around so that i didn't load the information until after the app delegate was loaded and it works now.