iOS settings page helper class - ios

I am trying to build a settings page helper class in order to simplify the setup of a settings page.
The idea would be that the class handles saving the state to UserDefaults and setting the initial state of any UISwitch.
Setting up a switch would just be a matter of setting a new switch to a class of "UISettingsSwitch" and adding the name of it to the accessibility label (it's the only identifier available as far as i'm aware).
So far I have :
import Foundation
import UIKit
class SettingsUISwitch: UISwitch {
enum SettingsType {
case darkMode, sound
}
func ison(type: SettingsType ) -> Bool {
switch type {
case .darkMode:
return userDefaults.bool(forKey: "darkMode")
case .sound:
return userDefaults.bool(forKey: "sound")
}
}
let userDefaults = UserDefaults.standard
override init(frame: CGRect) {
super.init(frame: frame)
initSwitch()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
initSwitch()
}
deinit {
}
func initSwitch() {
addTarget(self, action: #selector(toggle), for: .valueChanged)
}
#objc func toggle(){
userDefaults.setValue(self.isOn, forKey: self.accessibilityLabel!)
}
}
Not an awful lot I know.
I can currently do :
if settingsSwitch.ison(type: .darkMode) {
print (settingsSwitch.ison(type: .darkMode))
print ("ON")
} else {
print ("OFF")
}
The accessibility label doesn't seem to be available in the init setup at any point, so setting up the initial state doesn't seem to be a possibility.
Is it possible to set the initial state of the UISwitch this way ?
Ideally , I'd like to expose : settingsSwitch.darkMode.ison as a boolean ... but I can't figure that one out. Thanks for any help

I managed to use the restoration identifier to do the setup for the switch but I'd still love to remove the cases and the repeated calls to userDefaults
import Foundation
import UIKit
class UISwitchSettings: UISwitch {
enum SettingsType: String, CaseIterable {
case darkMode = "darkMode"
case sound = "sound"
}
func ison(type: SettingsType ) -> Bool {
switch type {
case .darkMode:
return userDefaults.bool(forKey: "darkMode")
case .sound:
return userDefaults.bool(forKey: "sound")
}
}
let userDefaults = UserDefaults.standard
override init(frame: CGRect) {
super.init(frame: frame)
initSwitch()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
initSwitch()
}
deinit {
}
func initSwitch() {
if let key = self.restorationIdentifier {
// Logic needs changing if default switch is off
if userDefaults.bool(forKey: key) || userDefaults.object(forKey: key) == nil {
self.isOn = true
} else {
self.isOn = false
}
}
addTarget(self, action: #selector(toggle), for: .valueChanged)
}
#objc func toggle(){
userDefaults.setValue(self.isOn, forKey: self.restorationIdentifier!)
}
}

Related

Swift Cannot convert return expression of type "CameraScreen" to return type 'UIView?'

I am trying to make a swift camera screen so that I can use it in react native. I keep running into this error Cannot convert return expression of type "CameraScreen" to return type 'UIView?'
I tried changing UIViewController to UIView and had more errors. I am extremely new to swift. So if anyone can help me resolve my error in trying to make a custom camera screen I will be extremely grateful.
This is my code:
// CameraScreen.swift
import Foundation
import UIKit
import AVFoundation
//#objc(CameraScreen)
class CameraScreen : UIViewController {
//capture session
var session: AVCaptureSession?
//photo output
let output = AVCapturePhotoOutput()
//video preview
let previewLayer = AVCaptureVideoPreviewLayer()
// Shutter button
private let shutterButton: UIButton = {
let button = UIButton(frame: CGRect(x: 0, y: 0, width: 200, height: 200))
button.layer.cornerRadius = 100
button.layer.borderWidth = 10
button.layer.borderColor = UIColor.white.cgColor
return button
}()
// override func viewDidLoad(){
// super.viewDidLoad()
// view.backgroundColor = .black
// checkCameraPermissons()
// }
override init(frame: CGRect) {
super.init(frame: frame)
checkCameraPermissons()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
checkCameraPermissons()
}
private func checkCameraPermissons(){
switch AVCaptureDevice.authorizationStatus(for: .video){
case .notDetermined:
// Request
AVCaptureDevice.requestAccess(for: .video) { [weak self] granted in
guard granted else{
return
}
DispatchQueue.main.async {
self?.setUpCamera()
}
}
case .restricted:
break
case .denied:
break
case .authorized:
setUpCamera()
#unknown default:
break
}
}
private func setUpCamera(){
let session = AVCaptureSession()
if let device = AVCaptureDevice.default(for: .video){
do{
let input = try AVCaptureDeviceInput(device:device)
if session .canAddInput(input){
session.addInput(input)
}
if session.canAddOutput(output){
session.addOutput(output)
}
previewLayer.videoGravity = .resizeAspectFill
previewLayer.session = session
session.startRunning()
self.session = session
}
catch{
print(error)
}
}
}
}
#objc (RCTCameraScreenManager)
class RCTCameraScreenManager: RCTViewManager {
override static func requiresMainQueueSetup() -> Bool {
return true
}
override func view() -> UIView! {
return CameraScreen(coder: <#NSCoder#>) // this is the line with the error
}
}

How to change language (localisation) within the app in swift 5?

I am trying to localise iOS app which is developed in Swift 5. I have done with all localisation things in code as well as in storyboard. But I am not sure how to change language within the app when i click on Language Button.
Is this possible to change app language within app? if yes How?
Please suggest best possible way to do same
I just did a similar implementation. Glad you asked and I saw this. Here is my implementation. You can modify.
enum Language: String, CaseIterable {
case english, german
var code: String {
switch self {
case .english: return "en"
case .german: return "de"
}
}
static var selected: Language {
set {
UserDefaults.standard.set([newValue.code], forKey: "AppleLanguages")
UserDefaults.standard.set(newValue.rawValue, forKey: "language")
}
get {
return Language(rawValue: UserDefaults.standard.string(forKey: "language") ?? "") ?? .english
}
}
static func switchLanguageBetweenEnglishAndGerman() {
selected = selected == .english ? .german : .english
}
}
Now you just need to call Language.selected == .german and reload the views.
To change localization throughout the app. For that, You need to follow the below step.
Create a Parent class of every UIViewController and define setupLocasitation method for further usage.
ParentViewController.swift
class ParentViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
func setupLocasitation(){
}
}
All other class of UIViewController should be a subclass of ParentViewController and override setupLocasitation method
ViewController1.swift
class ViewController1: ParentViewController {
override func viewDidLoad() {
super.viewDidLoad()
setupLocasitation()
}
override func setupLocasitation() {
super.setupLocasitation()
print("Your localisation specifi code here...")
}
}
ViewController2.swift
class ViewController2: ParentViewController {
override func viewDidLoad() {
super.viewDidLoad()
setupLocasitation()
}
override func setupLocasitation() {
super.setupLocasitation()
print("Your localisation specifi code here...")
}
}
ChangeLanguageVC.swift
You need to grab all instances of ParentViewController and force-fully call the setupLocasitation method.
class ChangeLanguageVC: ParentViewController {
override func viewDidLoad() {
super.viewDidLoad()
setupLocasitation()
}
#IBAction func btnChangeLanguageTap(){
//Code for your language changes here...
let viewControllers = self.navigationController?.viewControllers ?? []
for vc in viewControllers{
if let parent = vc as? ParentViewController{
parent.setupLocasitation()
}
}
}
}
//
// LanguageExtensions.swift
// Flourish
//
// Created by Janko on 11/11/2020.
//
import Foundation
import UIKit
let languageKey = "languageKey"
var language : Int {
switch UserDefaults.standard.string(forKey: languageKey) {
case "en":
return 0
case "dutch":
return 1
default:
return 0
}
}
extension String {
func localizedLanguage()->String?{
var defaultLanguage = "en"
if let selectedLanguage = UserDefaults.standard.string(forKey: languageKey){
defaultLanguage = selectedLanguage
}
return NSLocalizedString(self, tableName: defaultLanguage, comment: "")
}
}
class LanguageLabel: UILabel{
required init?(coder: NSCoder) {
super.init(coder: coder)
NotificationCenter.default.addObserver(self, selector: #selector(updateUI), name: AppNotification.changeLanguage, object: nil)
}
#IBInspectable var localizedLanguage: String? {
didSet{
updateUI()
}
}
#objc func updateUI(){
if let string = localizedLanguage {
text = string.localizedLanguage()
}
}
}
class LanguageButton: UIButton{
required init?(coder: NSCoder) {
super.init(coder: coder)
NotificationCenter.default.addObserver(self, selector: #selector(updateUI), name: AppNotification.changeLanguage, object: nil)
}
#IBInspectable var localizedLanguage: String? {
didSet{
updateUI()
}
}
#objc func updateUI(){
if let string = localizedLanguage {
setTitle(string.localizedLanguage(), for: .normal)
}
}
}
struct AppNotification{
static let changeLanguage = Notification.Name("changeLanguage")
}
extension UIViewController{
func changeLanguage(){
let alert = UIAlertController(title: "Change Language", message: "Change it", preferredStyle: .alert)
let actionEnglish = UIAlertAction(title: "English", style: .default) { (action) in
UserDefaults.standard.setValue("en", forKey: languageKey)
NotificationCenter.default.post(name: AppNotification.changeLanguage , object: nil)
}
let actionMontenegrin = UIAlertAction(title: "Montenegrinish", style: .default) { (action) in
UserDefaults.standard.setValue("dutch", forKey: languageKey)
NotificationCenter.default.post(name: AppNotification.changeLanguage , object: nil)
}
alert.addAction(actionEnglish)
alert.addAction(actionMontenegrin)
present(alert, animated: true, completion: nil)
}
}

Call a function from SwiftUI in UIHostingController

I have a swift UIHostingController which renders a SwiftUI. I call a function in view did appear which builds fine but doesn't create the intended output.
class LoginView: UIHostingController<LoginViewComponent> {
required init?(coder: NSCoder) {
super.init(coder: coder, rootView: LoginViewComponent())
}
override func viewDidAppear(_ animated: Bool) {
sessionHandler()
}
func sessionHandler(){
let user = User()
if user.isLoggedIn(){
view.isUserInteractionEnabled = false
print("Authenticating Session")
self.rootView.loginState(state: "success")
}else{
view.isUserInteractionEnabled = true
print("Needs Logging in")
}
}
}
The function ("loginState(state: "success")") works when called within the SwiftUI view class, however when called from the hosting controller it doesn't work.
Any help would be greatly appreciated.
SwiftUI is reactive state-base machine, actually, and all views are struct values, so you need to change concept, instead of imperatively send messages specify state dependency and reactions on those states...
Thus, keeping your custom-host-controller it could be set up like the following
import SwiftUI
import UIKit
import Combine
// model that keeps login state
class LoginState: ObservableObject {
#Published var state: String = ""
}
struct LoginViewComponent: View {
#EnvironmentObject var loginState: LoginState // state to be observed
...
// somewhere in body you use loginState.state
// and view will be refreshed depending on state changes
}
class LoginView: UIHostingController<AnyView> {
let loginState = LoginState() // here it is created
required init?(coder: NSCoder) {
super.init(coder: coder, rootView: AnyView(LoginViewComponent().environmentObject(self.loginState))) // here the ref injected
}
override func viewDidAppear(_ animated: Bool) {
sessionHandler()
}
func sessionHandler(){
let user = User()
if user.isLoggedIn(){
view.isUserInteractionEnabled = false
print("Authenticating Session")
self.loginState.state = "success" // here state changed
}else{
view.isUserInteractionEnabled = true
print("Needs Logging in")
}
}
}

Custom init UIViewController query

I am hoping you can help me understand why the below code segment works and the other does not. I am wanting to create a custom initialiser for my UIViewController which has a custom nib file I have created.
My issue is that I want to understand why in the below code the references to newMember and facebookLogin are retained when I hit the viewDidLoad method but in the other segment of code they are not? Can anyone shed some light as to why this would be the case?
Working Code Block
class RegistrationFormViewController: MiOSBaseViewController
{
var newMember:Member!
var facebookLogin: Bool = false
init(member: Member, facebookLogin: Bool = false) {
self.newMember = member
self.facebookLogin = facebookLogin
super.init(nibName: "RegistrationFormViewController", bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
super.init(nibName: "RegistrationFormViewController", bundle: nil)
}
override func viewDidLoad() {
super.viewDidLoad()
let view = self.view as! RegistrationFormView
view.loadViewWith(member: newMember)
view.customNavBarView.backActionBlock = {
self.newMember.deleteEntity(MiOSDataContext.sharedInstance.managedObjectContext)
_ = self.navigationController?.popViewController(animated: true)
return
}
}
}
Broken Code Block
class RegistrationFormViewController: MiOSBaseViewController
{
var newMember:Member!
var facebookLogin: Bool = false
init(member: Member, facebookLogin: Bool = false) {
self.newMember = member
self.facebookLogin = facebookLogin
super.init(nibName: "RegistrationFormViewController", bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func viewDidLoad() {
super.viewDidLoad()
let view = self.view as! RegistrationFormView
view.loadViewWith(member: newMember)
view.customNavBarView.backActionBlock = {
self.newMember.deleteEntity(MiOSDataContext.sharedInstance.managedObjectContext)
_ = self.navigationController?.popViewController(animated: true)
return
}
}
}
Thanks,
Michael

How to send a complete closure better

What I want is to send a closures to a UIViewController to tell it what it will do in the end. But when it comes to a package of UIViewControllers it will be a little messy.
class ViewController: UIViewController {
private var complete: ()->()
init(complete: ()->()){
self.complete = complete
super.init(nibName: nil, bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class ViewController2: UIViewController {
private var complete: ()->()
init(complete: ()->()){
self.complete = complete
super.init(nibName: nil, bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Here are my UIViewControllers. What want to do is to send a complete closure to UIViewController2, but I have to push UIViewController first. So what I have to do is send the closure to UIViewController, then UIViewController send the closure to UIViewController2. It is not messy when there are only two UIViewControllers. But it will be very messy when there comes out a package of UIViewControllers.
Any better solutions?
You the following code to handle the completion handler
First, create a base class of UIViewController and define the completionHandler
import UIKit
public class MyController: UIViewController {
var completionHandler: ((Bool, String) -> Void)? // Bool and String are the return type, you can choose anything you want to return
public func onComplete(completion : ((Bool, String) -> Void)?)
{
self.completionHandler = completion
}
override public func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override public func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
In ViewController1, you need to call another ViewController like this
class ViewController: MyController {
// Initialization of view objects
override func viewDidLoad()
{
super.viewDidLoad()
//You are loading another ViewController from current ViewController
let controller2 = self.storyboard?.instantiateViewControllerWithIdentifier("controller2") as? MyController
controller2?.onComplete({ (finished, event) in
self.completionHandler?(finished, event)
})
}
//This is your button action or could be any event on which you will fire the completion handler
#IBAction func buttonTapped()
{
self.completionHandler(boolType, controllerName)
}
and where ever, you will create a new ViewController you will need to set its completionHandler by writing the line
controller2?.onComplete({ (finished, event) in
self.completionHandler?(finished, event)
})

Resources