How to create a class instance in Observer pattern? - ios

I implemented an observer pattern in the below code. However, I am not sure how to create SeasonSubject class's instance in order to call addObserver() function?
I don't want to create it inside my view controller. Please refer to below code.
//
// SeasonViewController.swift
// PhotoCalender
//
// Created by Suraj M Gaikwad on 09/07/21.
//
import UIKit
enum Season: String {
case summer
case winter
case monsoon
case none
}
protocol SeasonObserver {
func onSeasonChange(_season: Season)
}
class PhotoframeObserver: SeasonObserver {
var delegate: SeasonViewControllerDelegate?
init(_delegate: SeasonViewControllerDelegate) {
delegate = _delegate
}
func onSeasonChange(_season: Season) {
switch _season {
case .monsoon, .summer, .winter:
delegate?.changeTheLayoutPer(_season: _season)
case .none:
debugPrint("none")
}
}
}
class PhotoDetailsObserver: SeasonObserver {
var delegate: SeasonViewControllerDelegate?
init(_delegate: SeasonViewControllerDelegate) {
delegate = _delegate
}
func onSeasonChange(_season: Season) {
switch _season {
case .monsoon, .summer, .winter:
delegate?.changeTheTitle(_season: _season)
case .none:
debugPrint("none")
}
}
}
protocol SeasonSubjectProtocol {
func informTheSeasonChange(_season: Season)
}
class SeasonSubject: SeasonSubjectProtocol {
private var _season = Season.none
var changedSeason: Season {
get {
_season
}
set {
_season = newValue
}
}
private var seasonObserver = [SeasonObserver]()
func addObserver(_observer: SeasonObserver) {
seasonObserver.append(_observer)
}
func removeObserver(_observer: SeasonObserver) {
// seasonObserver.remove(at: 0)
}
private func notifyObserver() {
seasonObserver.forEach { $0.onSeasonChange(_season: _season)
}
}
func informTheSeasonChange(_season: Season) {
changedSeason = _season
}
deinit {
seasonObserver.removeAll()
}
}
protocol SeasonViewControllerDelegate {
func changeTheLayoutPer(_season: Season)
func changeTheTitle(_season: Season)
}
class SeasonViewController: UIViewController {
#IBOutlet weak var imageView: UIImageView!
#IBOutlet weak var backgroundView: UIView!
#IBOutlet weak var seasonTitle: UILabel!
var delegate: SeasonSubjectProtocol?
var count = 0
override func viewDidLoad() {
super.viewDidLoad()
self.imageView.image = UIImage()
self.backgroundView.backgroundColor = .white
self.seasonTitle.text = "none"
}
#IBAction func changeTheSeason(_ sender: UIButton) {
if count > 2 {
count = 0
return
}
if count == 0 {
delegate?.informTheSeasonChange(_season: Season.summer)
}
if count == 1 {
delegate?.informTheSeasonChange(_season: Season.winter)
}
if count == 2 {
delegate?.informTheSeasonChange(_season: Season.monsoon)
}
count += 1
}
}
extension SeasonViewController: SeasonViewControllerDelegate {
func changeTheLayoutPer(_season: Season) {
switch _season {
case .monsoon:
self.imageView.image = UIImage()
self.backgroundView.backgroundColor = .gray
case .summer:
self.imageView.image = UIImage()
self.backgroundView.backgroundColor = .orange
case .winter:
self.imageView.image = UIImage()
self.backgroundView.backgroundColor = .blue
case .none:
debugPrint("none")
self.imageView.image = UIImage()
self.backgroundView.backgroundColor = .white
}
}
func changeTheTitle(_season: Season) {
switch _season {
case .monsoon, .summer, .winter, .none:
self.seasonTitle.text = _season.rawValue
}
}
}
I want to create an instance of SeasonSubject() class. I want to call addObserver & removeObserver() methods.

You can create SeasonSubject as a singleton instance, it already maintains an array of observers so multiple observers can use this same instance throughout the app.
class SeasonSubject: SeasonSubjectProtocol {
static let shared = SeasonSubject()
}
All you need to do now is - call addObserver / removeObserver on this singleton instance from the places you want.
class ViewController: UIViewController {
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
SeasonSubject.shared.addObserver(self)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
SeasonSubject.shared.removeObserver(self)
}
}
CAUTION : You must make sure these add/remove calls are balanced otherwise you will fall into the trap of observers never getting deallocated.
If you want to stay away from this problem - you should consider a NotificationCenter based implementation where your observers are never at risk of being retained in memory forever.

Related

ios Swift Protocol Data

I don't use storyboards.
I want to send protocol data using #objc button action.
However, the sent view controller does not run the protocol function.
May I know what the reason is?
In fact, there's a lot more code.
Others work, but only protocol functions are not executed.
The didUpdataChampion function is
Data imported into a different protocol.
I have confirmed that there is no problem with this.
protocol MyProtocolData {
func protocolData(dataSent: String)
func protocolCount(dataInt: Int)
}
class PickViewController: UIViewController,ChampionManagerDelegate{
static let identifier = "PickViewController"
var count = 0
var urlArray = [URL]()
var pickDelegate : MyProtocolData?
override func viewDidLoad() {
super.viewDidLoad()
champions.riot(url: "myURL")
}
#objc func topHand(){
pickDelegate?.protocolData(dataSent: "top")
print(count)
pickDelegate?.protocoCount(dataInt: count)
let cham = ChampViewController()
cham.modalPresentationStyle = .fullScreen
present(cham, animated: true, completion: nil)
}
//Data imported to another protocol
func didUpdataChampion(_ championManager: ChampionManager, champion: [ChampionRiot]) {
print(#function)
count = champion.count
for data in champion {
let id = data.id
guard let url = URL(string: "https://ddragon.leagueoflegends.com/cdn/11.16.1/img/champion/\(id).png") else { return }
urlArray.append(url)
count = urlArray.count
}
}
func didFailWithError(error: Error) {
print(error)
}
}
class ChampViewController: UIViewController,MyProtocolData {
var pickData = ""
var arrayCount = 0
override func viewDidLoad() {
super.viewDidLoad()
}
func protocolData(dataSent: String) {
print(#function)
pickData = dataSent
print(pickData)
}
func protocoCount(dataInt: Int) {
print(#function)
arrayCount = dataInt
print(arrayCount)
}
}
i don't see full code, for instance how you call bind to topHand(), my advice is:
check that topHand - is called
check that pickDelegate isn't nil inside topHand
Create Object fo your PickViewController class and set its delegate to self.
var yourObj = PickViewController()
override func viewDidLoad() {
super.viewDidLoad()
yourObj.delegate = self
}

Delegate function does not get called

I have two ViewControllers and I'm trying to set one as the other's delegate. This is what I have:
ViewController One:
protocol storeChosenDelegate {
func getPopularProductsFor(store id: String)
}
class PopularStoresVC: UIViewController {
//MARK: - Properties
var delegate: storeChosenDelegate?
private let storesView = PopularStoresView()
private let STORE_CELL = "storeCell"
fileprivate var currentStore: Int = 0 {
didSet {
delegate?.getPopularProductsFor(store: "THIS IS WORKING NOW.")
}
}
}
And this is what I have in ViewController Two:
//MARK: - Properties
private let PRODUCT_CELL = "productCell"
private var popularStores = PopularStoresVC()
//MARK: - Initializers
override func viewDidLoad() {
super.viewDidLoad()
popularStores.delegate = self
setupProductsCollection()
}
extension PopularProductsVC: storeChosenDelegate {
func getPopularProductsFor(store id: String) {
//TODO: Show all popular products for the store's id we got.
print("Got store \(id)")
}
}
It seems that the didSet is getting called, and I do set the Second VC as the delegate, but the function just does not getting called. I have no errors or warnings related to that so I don't really understand why this is not working.

Sinch video doen't want to work on iOS (Swift)

So basically I want to enable Sinch Video in iOS application.
For testing purposes I've created SinchManaevger which is singleton and I instatiate it in AppDelegate:
class SinchManager: NSObject, SINClientDelegate, SINCallClientDelegate {
static let sharedInstance = SinchManager()
var client: SINClient?
func initSinchClientWithUserId(id: String) {
if client == nil {
if case .Authenticated(let currentUser, _) = SessionManager.sharedInstance.state.value {
self.client = Sinch.clientWithApplicationKey("xyz", applicationSecret: "xyz", environmentHost: "sandbox.sinch.com", userId: currentUser.username)
print("sinchClient")
print(client!)
self.client!.delegate = self
self.client!.setSupportCalling(true)
self.client!.enableManagedPushNotifications()
self.client!.start()
self.client!.startListeningOnActiveConnection()
}
}
}
func clientDidStart(client: SINClient!) {
print("clientDidStart")
self.client!.callClient().delegate = self
}
func clientDidStop(client: SINClient!) {
print("clientDidStop")
}
func clientDidFail(client: SINClient!, error: NSError!) {
print("clientDidFail")
}
func client(client: SINCallClient!, didReceiveIncomingCall call: SINCall!) {
print("didReceiveIncomingCall")
let sinchVC = SinchVC(username: currentUser.username)
let sinchNC = DNMMainNC(rootViewController: sinchVC)
sinchVC.call = call
}
}
And I've created Sinch ViewController which is initialized with username which will be called:
class SinchVC: UIViewController, SINCallDelegate {
private let videoController = SinchManager.sharedInstance.client!.videoController()
private let audioController = SinchManager.sharedInstance.client!.audioController()
private let callClient: SINCallClient
private var call: SINCall!
let username: String
private var mainView: SinchView { return view as! SinchView }
override func loadView() {
view = SinchView()
}
init(username: String) {
self.username = username
self.callClient = SinchManager.sharedInstance.client!.callClient()
super.init(nibName: nil, bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
call.delegate = self
self.mainView.videoView.addSubview(self.videoController.localView())
self.videoController.localView().contentMode = .ScaleToFill
if self.call.direction == SINCallDirection.Incoming {
self.audioController.startPlayingSoundFile(self.pathForSound("incoming.wav") as String, loop: true)
}
if self.call.details.videoOffered {
print("video offered")
self.mainView.videoView.addSubview(self.videoController.localView())
self.videoController.localView().contentMode = .ScaleToFill
}
mainView.videoView.addSubview(self.videoController.localView())
mainView.answerButton.addTarget(self, action: #selector(answer), forControlEvents: .TouchUpInside)
mainView.declineButton.addTarget(self, action: #selector(decline), forControlEvents: .TouchUpInside)
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
self.audioController.enableSpeaker()
}
func pathForSound(string: String) -> NSString {
let nsSt = NSBundle.mainBundle().resourcePath! as NSString
return nsSt.stringByAppendingPathComponent(string)
}
func answer() {
call.answer()
}
func decline() {
call.hangup()
}
func callDidEstablish(call: SINCall!) {
print("callDidEstablish")
}
func callDidEnd(call: SINCall!) {
print("callDidEnd")
}
func callDidProgress(call: SINCall!) {
print("callDidProgress")
self.audioController.startPlayingSoundFile(self.pathForSound("ringback.wav") as String, loop: true)
}
func callDidAddVideoTrack(call: SINCall!) {
print("callDidAddVideoTrack")
mainView.videoView.addSubview(self.videoController.remoteView())
}
}
Problem is when I try to call from my app to other phone with my app nothing happens (didReceiveIncomingCall delegate method doesn't get called at all)
If I try to call from my app to SinchVideo sample app then video call gets initiated normal. But when i call from SinchVideo app to my app nothing happens in my app. So probably i've forgot to add some notification or something to tell my app when the call is incoming. If you could help I would be very grateful. Thanks
EDIT: I managed to make didReceiveIncomingCall work but now call.answer isnt working. (nothing happens when call.answer is called and i see that my phone is ringing)
I am not sure what DNMMainNC does in your did recieve incoming call,
let sinchNC = DNMMainNC(rootViewController: sinchVC) What does DNMMainNC do?
sinchVC.call = call // private var?
But its looks kind of weird to set a private var call from your code, should that not be public or have a constructor like your init but with a call

EXC_BAD_ACCESS Code=2 Swift iOS

When developing an iOS app I come across random (sometimes occurring, sometimes not) EXC_BAD_ACCESS error in the following code:
import UIKit
class OrderTripDetailsController: UIViewController, OrderAware {
var order: Order?
var orderService = OrderService()
// MARK: Properties
#IBOutlet weak var driverName: UILabel!
#IBOutlet weak var autoColor: UILabel!
#IBOutlet weak var autoPlates: UILabel!
#IBOutlet weak var autoBrandModel: UILabel!
#IBOutlet weak var map: YMKMapView!
// MARK: Actions
// MARK: Navigation
override func viewDidLoad() {
if let cab = order?.orderCab {
autoColor.text = cab.cab.auto.color.name
autoPlates.text = cab.cab.auto.plates
driverName.text = cab.cab.driver.fullname
autoBrandModel.text = "\(cab.cab.auto.brand) \(cab.cab.auto.model)"
}
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
orderService.controllerDelegate = self
orderService.startSync()
}
override func viewWillDisappear(animated: Bool) {
orderService.stop() // <-- this is the line that fails
super.viewWillDisappear(animated)
}
// MARK: Order Aware
func setOrder(order: Order){
self.order = order
}
}
OrderService
Order Service is a class that starts timer and stops it. After stop is called, sometimes EXC_BAD_ACCESS occurs
import Locksmith
import RealmSwift
class OrderService {
// MARK: delegates
var controllerDelegate: UIViewController?
// Get the default Realm
let realm = try! Realm()
var timer = NSTimer()
//MARK: Domain actions
//repeat every n time if any dfound
#objc func initOrderSync(timer:NSTimer) {
func onRemoteReceived (order: Order) -> Void {
debugPrint("remote received", order.id)
if let o = realm.objectForPrimaryKey(OrderPo.self, key: order.orderHash) {
if o.orderStatus != order.orderStatus {
debugPrint("order status updated", order.id, order.orderStatus)
OrderUtils.navigateToOrderStatus(order,
viewController: controllerDelegate!)
}
try! realm.write {
o.orderStatus = order.orderStatus
}
}
}
if let authToken = AuthUtils.getToken() {
let orders = getOrdersByStatuses(OrderUtils.activeStatuses)
if orders.count > 0 {
for o in orders {
OrderRemoteService().getOrderFromRemote(o.id!, token: authToken, callback: onRemoteReceived)
}
} else {
debugPrint("invalidating timer; no orders")
stop()
}
} else {
debugPrint("invalidating timer; no auth")
stop()
}
}
func startSync() {
if !self.timer.valid {
debugPrint("starting order sync", controllerDelegate?.restorationIdentifier)
self.timer = NSTimer.scheduledTimerWithTimeInterval(20, target: self, selector: Selector("initOrderSync:"), userInfo: nil, repeats: true)
} else {
debugPrint("sync is already started", controllerDelegate?.restorationIdentifier)
}
}
func stop() {
dispatch_async(GlobalMainQueue, {
if self.timer.valid {
self.timer.invalidate()
debugPrint("invalidated timer", self.controllerDelegate?.restorationIdentifier)
} else {
debugPrint("sync is already stopped", self.controllerDelegate?.restorationIdentifier)
}
})
}
// MARK: orders
func getOrders() -> Results<OrderPo> {
return try! realm.objects(OrderPo.self).sorted("orderTime")
}
func getOrdersByStatuses(statuses: [String]) -> Results<OrderPo> {
var qString = "'\(statuses.first!)'"
if statuses.count > 1 {
for s in 1...statuses.count-1 {
qString += ",'\(statuses[s])'"
}
}
return try! realm.objects(OrderPo.self).filter("orderStatus IN {\(qString)}").sorted("orderTime")
}
}
Could anyone help with any ideas why it might happen?
Update 20.08.2016
Found out that OrderService is being deinitialised after 10 seconds for some reason.

IBOutlet always nil in ViewController methods but ok in IBAction & Viewdidload

I try to access some IBOutlet outside of Viewdidload and IBAction, and always get nil value. In Viewdidload and IBAction, those value are ok. Did i miss a part to declare or initialize something ?
The value are modified after viewdidload() because viewdidload is called bu the IBAction.
The View is created in storyboard, coming from a UINavigation Controller.
connection table between ViewController and UIView:
The loginServer method is called by userCredential delegate, as below:
protocol userCredentialDelegate {
func didUpdateCredential (sender:String, credential: Bool?)
}
class userCredential: NSObject {
var delegate:userCredentialDelegate?
// self.delegate = ViewController() removed
func loginServer (name: String, pwd: String) -> Bool {
dispatch_sync(dispatch_get_main_queue())
{
self.delegate?.didUpdateCredential ("login", credential: credentialStatus)
}
}
Main controller:
class ViewController: UIViewController, userCredentialDelegate {
// set the shared instance
let user = userCredential.sharedInstance
#IBOutlet weak var incorrectCredentials: UITextField!
#IBOutlet weak var username: UITextField!
#IBOutlet weak var password: UITextField!
#IBOutlet weak var logButton: UIButton!
#IBAction func logButton(sender: UIButton) {
print (incorrectCredentials?.hidden)
if logButton.titleLabel!.text == "Log Out" {
user.logoutServer ()
} else {
user.loginServer(username.text!, pwd: password.text!)
}
}
func didUpdateCredential (sender: String, credential: Bool?) {
switch sender {
case "login":
if credential! {
performSegueWithIdentifier("loginSegue", sender: self)
} else {
incorrectCredentials?.hidden = false
}
default: break
}
if let credentialResponse = credential {
loginStatus = credentialResponse
}
}
var loginStatus: Bool = false {
didSet {
if loginStatus {
incorrectCredentials?.hidden = true // always nil before, now ok
} else {
incorrectCredentials?.hidden = false // always nil before, now ok
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
user.delegate = self
incorrectCredentials.hidden = true // can work here
user.getUserInfo ()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
connection table:
You need to set the delegate to your user model in your viewDidLoad function.
Before doing user.getUserInfo() make user.delegate = self
currently you create a new Instance on the user model, that has nothing todo with you real loaded ViewController.

Resources