How to encode/decode [CKRecordZone.ID: CKServerChangeToken]? - swift

public var zonesChangeToken: [CKRecordZone.ID: CKServerChangeToken]? {
get {
if(backingPreviousZonesChangeToken == nil) {
guard let defaults: UserDefaults = UserDefaults(suiteName: CloudKitHandler.APP_GROUP_ID) else { return nil }
guard let data = defaults.data(forKey: CloudKitHandler.CK_PREVIOUS_ZONES_CHANGE_TOKEN)
else { return [CKRecordZone.ID: CKServerChangeToken]() }
do {
let unarchiver: NSKeyedUnarchiver = try NSKeyedUnarchiver(forReadingFrom: data)
unarchiver.requiresSecureCoding = true
backingPreviousZonesChangeToken = try unarchiver.decodeTopLevelObject() as? [CKRecordZone.ID: CKServerChangeToken]
} catch { }
}
return backingPreviousZonesChangeToken
}
set(value) {
backingPreviousZonesChangeToken = value
guard let value = value else { return }
guard let defaults: UserDefaults = UserDefaults(suiteName: CloudKitHandler.APP_GROUP_ID) else { return }
let archiver: NSKeyedArchiver = NSKeyedArchiver(requiringSecureCoding: true)
archiver.encode(value)
archiver.finishEncoding()
defaults.setValue(archiver.encodedData, forKey: CloudKitHandler.CK_PREVIOUS_ZONES_CHANGE_TOKEN)
}
}
I'm trying to encode/decode a dictionary of IDs and Tokens. But for some reason the decode always gives me a nil.
How to fix?

extension CKServerChangeToken {
func dataRepresentation() -> Data {
let coder = NSKeyedArchiver.init(requiringSecureCoding: true)
coder.requiresSecureCoding = true
self.encode(with: coder)
coder.finishEncoding()
return coder.encodedData
}
class func token(data: Data) -> CKServerChangeToken? {
do{
let coder = try NSKeyedUnarchiver(forReadingFrom: data)
coder.requiresSecureCoding = true
let record = CKServerChangeToken(coder: coder)
coder.finishDecoding()
return record
} catch {
print(error)
}
return nil
}
}

Related

Am I using firebase api incorrectly?

Whenever paging in tableview, the view model is running fetchDataRx. It works well, but I don't think it's right to sort the entire data every time you paginate and call the addSnapShotListener. If my code is not correct, how can I correct it?
// MARK: ViewController.swift
timeLineTableView.rx.didScroll
.withLatestFrom(viewModel.activated)
.subscribe(onNext: { [weak self] isActivated in
if !isActivated {
guard let self = self else { return }
let position = self.timeLineTableView.contentOffset.y
if position > self.timeLineTableView.contentSize.height - 100 - self.timeLineTableView.frame.size.height {
self.viewModel.fetchPosts.onNext(())
}
}
})
.disposed(by: disposeBag)
//MARK: ViewModel.swift
let fetchPosts: AnyObserver<Void>
let fetching = PublishSubject<Void>()
fetchPosts = fetching.asObserver()
fetching
.do(onNext: { _ in activating.onNext(true) })
.withLatestFrom(posts)
.map { $0.count }
.flatMap{ (count) -> Observable<[post]> in
fireBaseService.fetchDataRx(startIdx: count) }
.map { $0.map { ViewPost(post: $0) } }
.do(onNext: { _ in activating.onNext(false) })
.do(onError: { err in error.onNext(err) })
.subscribe(onNext: { newPosts in
let oldData = posts.value
posts.accept(oldData + newPosts)
})
.disposed(by: disposeBag)
//MARK: FirebaseService.swift
protocol FirebaseServiceProtocol {
func fetchDataRx(startIdx: Int) -> Observable<[post]>
func fetchData(startIdx: Int, completion: #escaping (Result<[post], Error>) -> Void)
}
class FireBaseService: FirebaseServiceProtocol {
func fetchDataRx(startIdx: Int) -> Observable<[post]> {
return Observable.create { (observer) -> Disposable in
self.fetchData(startIdx: startIdx) { result in
switch result {
case .success(let data):
observer.onNext(data)
case .failure(let error):
observer.onError(error)
}
observer.onCompleted()
}
return Disposables.create()
}
}
func fetchData(startIdx: Int, completion: #escaping (Result<[post], Error>) -> Void) {
let db = Firestore.firestore()
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd HH:mm"
if startIdx == 0 {
DispatchQueue.global().async {
let first = db.collection("lolCourt")
.order(by: "date")
.limit(to: 8)
var nextPosts = [post]()
first.getDocuments() { (querySnapshot, error) in
if let error = error {
print("Error getting documents: \(error)")
} else {
for document in querySnapshot!.documents {
guard let url = document.data()["url"] as? String else {
continue
}
guard let champion1 = document.data()["champion1"] as? String else {
continue
}
guard let champion1Votes = document.data()["champion1Votes"] as? Double else {
continue
}
guard let champion2 = document.data()["champion2"] as? String else {
continue
}
guard let champion2Votes = document.data()["champion2Votes"] as? Double else {
continue
}
guard let text = document.data()["text"] as? String else {
continue
}
guard let date = document.data()["date"] as? Double else {
continue
}
nextPosts.append(post(url: url,
champion1: champion1,
champion1Votes: champion1Votes,
champion2: champion2,
champion2Votes: champion2Votes,
text: text,
date: formatter.string(from: Date(timeIntervalSince1970: date))))
}
}
completion(.success(nextPosts))
}
}
}
else {
DispatchQueue.global().async {
let first = db.collection("lolCourt")
.order(by: "date")
.limit(to: startIdx)
first.addSnapshotListener { (snapshot, error) in
guard let snapshot = snapshot else {
print("Error retrieving : \(error.debugDescription)")
return
}
guard let lastSnapshot = snapshot.documents.last else {
return
}
let next = db.collection("lolCourt")
.order(by: "date")
.start(afterDocument: lastSnapshot)
.limit(to: 8)
var nextPosts = [post]()
next.getDocuments() { (querySnapshot, error) in
if let error = error {
print("Error getting documents: \(error)")
} else {
for document in querySnapshot!.documents {
guard let url = document.data()["url"] as? String else {
continue
}
guard let champion1 = document.data()["champion1"] as? String else {
continue
}
guard let champion1Votes = document.data()["champion1Votes"] as? Double else {
continue
}
guard let champion2 = document.data()["champion2"] as? String else {
continue
}
guard let champion2Votes = document.data()["champion2Votes"] as? Double else {
continue
}
guard let text = document.data()["text"] as? String else {
continue
}
guard let date = document.data()["date"] as? Double else {
continue
}
nextPosts.append(post(url: url,
champion1: champion1,
champion1Votes: champion1Votes,
champion2: champion2,
champion2Votes: champion2Votes,
text: text,
date: formatter.string(from: Date(timeIntervalSince1970: date))))
}
}
completion(.success(nextPosts))
}
}
}
}
}
}
I think you are doing it wrong... I would expect to see something more like this:
class FireBaseService {
func getPage<T>(query: Query? = nil, build: #escaping ([String: Any]) -> T) -> Observable<([T], nextPage: Query?)> {
Observable.create { observer in
let db = Firestore.firestore()
let page = query ?? db.collection("lolCourt")
.order(by: "date")
.limit(to: 8)
let listener = page
.addSnapshotListener { snapshot, error in
guard let snapshot = snapshot else { observer.onError(error ?? RxError.unknown); return }
let items = snapshot.documents.map { build($0.data()) }
if let lastSnapshot = snapshot.documents.last {
let next = page
.start(afterDocument: lastSnapshot)
observer.onSuccess((items, nextPage: next))
}
else {
observer.onSuccess((items, nextPage: nil))
}
}
return Disposables.create { listener.remove() }
}
}
}
Use the above in your favorite state machine system. Here is an example using my CLE library.
// in view controller
let fireBaseService = FireBaseService()
let activityIndicator = ActivityIndicator()
let errorRouter = ErrorRouter()
func getPage(nextPage: Query?) -> Observable<([Post?], nextPage: Query?)> {
fireBaseService.getPage(query: nextPage, build: Post.init(dict:))
.rerouteError(errorRouter)
.trackActivity(activityIndicator)
}
let posts = cycle(
inputs: [
getPage(nextPage: nil).map(ViewModel.Input.response),
timeLineTableView.rx.reachedBottom(offset: 20).map(to: ViewModel.Input.next)
],
initialState: ([Post?](), nextPage: Query?.none),
environment: getPage(nextPage:),
reduce: ViewModel.reduce(state:input:getPage:)
)
.map { $0.0.compactMap { $0 } }
and the view model:
enum ViewModel {
enum Input {
case response([Post?], nextPage: Query?)
case next
}
static func reduce(state: inout ([Post?], nextPage: Query?), input: Input, getPage: #escaping (Query) -> Observable<([Post?], nextPage: Query?)>) -> Observable<Input> {
switch input {
case let .response(posts, nextPage):
state.0 += posts
state.nextPage = nextPage
case .next:
guard let nextPage = state.nextPage else { break }
return getPage(nextPage)
.map(Input.response)
}
return .empty()
}
}

I can print data but can't assign it to a label in Swift

I sent my data from my API call to my InfoController viewDidLoad. There, I was able to safely store it in a skillName constant, and also printed it, receiving all the information by console.
The problem comes when I try to assign this variable to my skillLabel.
override func viewDidLoad() {
super.viewDidLoad()
configureViewComponents()
fetchPokemons { (names) in
guard var skillName = names as? String else { return }
self.pokemon?.skillName = skillName
self.allNames = skillName
print(self.allNames)
}
}
There, when I print allNames, the console shows all the data I need. This is how the data looks like: Data Example
And the computed property where I wanna use this data looks is:
var pokemon: Pokemon? {
didSet {
guard let id = pokemon?.id else { return }
guard let data = pokemon?.image else { return }
navigationItem.title = pokemon?.name?.capitalized
infoLabel.text = pokemon?.description
infoView.pokemon = pokemon
if id == pokemon?.id {
imageView.image = UIImage(data: data)
infoView.configureLabel(label: infoView.skillLabel, title: "Skills", details: "\(allNames)")
}
}
}
PD: allNames is a String variable I have at InfoController class-level.
This is how my app looks when run:
PokeApp
My goal is to get that details param to show the skillName data, but it returns nil, idk why. Any advice?
EDIT1: My func that fetches the Pokemon data from my service class is this one:
func fetchPokemons(handler: #escaping (String) -> Void) {
controller.service.fetchPokes { (poke) in
DispatchQueue.main.async {
self.pokemon? = poke
guard let skills = poke.abilities else { return }
for skill in skills {
guard let ability = skill.ability else { return }
guard var names = ability.name!.capitalized as? String else { return }
self.pokemon?.skillName = names
handler(names)
}
}
}
}
EDIT2: InfoView class looks like:
class InfoView: UIView {
// MARK: - Properties
var delegate: InfoViewDelegate?
// This whole block assigns the attributes that will be shown at the InfoView pop-up
// It makes the positioning of every element possible
var pokemon: Pokemon? {
didSet {
guard let pokemon = self.pokemon else { return }
guard let type = pokemon.type else { return }
guard let defense = pokemon.defense else { return }
guard let attack = pokemon.attack else { return }
guard let id = pokemon.id else { return }
guard let height = pokemon.height else { return }
guard let weight = pokemon.weight else { return }
guard let data = pokemon.image else { return }
if id == pokemon.id {
imageView.image = UIImage(data: data)
}
nameLabel.text = pokemon.name?.capitalized
configureLabel(label: typeLabel, title: "Type", details: type)
configureLabel(label: pokedexIdLabel, title: "Pokedex Id", details: "\(id)")
configureLabel(label: heightLabel, title: "Height", details: "\(height)")
configureLabel(label: defenseLabel, title: "Defense", details: "\(defense)")
configureLabel(label: weightLabel, title: "Weight", details: "\(weight)")
configureLabel(label: attackLabel, title: "Base Attack", details: "\(attack)")
}
}
let skillLabel: UILabel = {
let label = UILabel()
return label
}()
let imageView: UIImageView = {
let iv = UIImageView()
iv.contentMode = .scaleAspectFill
return iv
}()
. . .
}
infoView.configureLabel is this:
func configureLabel(label: UILabel, title: String, details: String) {
let attributedText = NSMutableAttributedString(attributedString: NSAttributedString(string: "\(title): ", attributes: [NSAttributedString.Key.font : UIFont.boldSystemFont(ofSize: 16), NSAttributedString.Key.foregroundColor: Colors.softRed!]))
attributedText.append(NSAttributedString(string: "\(details)", attributes: [NSAttributedString.Key.font : UIFont.systemFont(ofSize: 16), NSAttributedString.Key.foregroundColor: UIColor.gray]))
label.attributedText = attributedText
}
EDIT 3: Structures design
struct Pokemon: Codable {
var results: [Species]?
var abilities: [Ability]?
var id, attack, defense: Int?
var name, type: String?
...
}
struct Ability: Codable {
let ability: Species?
}
struct Species: Codable {
let name: String?
let url: String?
}
Jump to the Edit2 paragraph for the final answer!
Initial Answer:
I looks like you UI does not get updated after the controller fetches all the data.
Since all of you UI configuration code is inside the var pokemon / didSet, it's a good idea to extract it to a separate method.
private func updateView(with pokemon: Pokemon?, details: String?) {
guard let id = pokemon?.id, let data = pokemon?.image else { return }
navigationItem.title = pokemon?.name?.capitalized
infoLabel.text = pokemon?.description
infoView.pokemon = pokemon
if id == pokemon?.id {
imageView.image = UIImage(data: data)
infoView.configureLabel(label: infoView.skillLabel, title: "Skills", details: details ?? "")
}
}
and now you can easily call in the the didSet
var pokemon: Pokemon? {
didSet { updateView(with: pokemon, details: allNames) }
}
and fetchPokemons completion aswell
override func viewDidLoad() {
super.viewDidLoad()
configureViewComponents()
fetchPokemons { (names) in
guard var skillName = names as? String else { return }
self.pokemon?.skillName = skillName
self.allNames = skillName
print(self.allNames)
DispatchQueue.main.async {
self.updateView(with: self.pokemon, details: self.allNames)
}
}
}
It's super important to do any UI setup on the main queue.
Edit:
The fetch function may be causing the problems! you are calling handler multiple times:
func fetchPokemons(handler: #escaping (String) -> Void) {
controller.service.fetchPokes { (poke) in
DispatchQueue.main.async {
self.pokemon? = poke
guard let skills = poke.abilities else { return }
let names = skills.compactMap { $0.ability?.name?.capitalized }.joined(separator: ", ")
handler(names)
}
}
}
Edit2:
After looking at your codebase there are a couple of things you need to change:
1. fetchPokemons implementation
the handler of controller.service.fetchPokes gets called for every pokemon so we need to check if the fetched one is the current (self.pokemon) and then call the handler with properly formated skills.
func fetchPokemons(handler: #escaping (String) -> Void) {
controller.service.fetchPokes { (poke) in
guard poke.id == self.pokemon?.id else { return }
self.pokemon? = poke
let names = poke.abilities?.compactMap { $0.ability?.name?.capitalized }.joined(separator: ", ")
handler(names ?? "-")
}
}
2. update viewDidLoad()
now simply pass the names value to the label.
override func viewDidLoad() {
super.viewDidLoad()
configureViewComponents()
fetchPokemons { (names) in
self.pokemon?.skillName = names
self.infoView.configureLabel(label: self.infoView.skillLabel, title: "Skills", details: names)
}
}
3. Refactor var pokemon: Pokemon? didSet observer
var pokemon: Pokemon? {
didSet {
guard let pokemon = pokemon, let data = pokemon.image else { return }
navigationItem.title = pokemon.name?.capitalized
infoLabel.text = pokemon.description!
infoView.pokemon = pokemon
imageView.image = UIImage(data: data)
}
}

WEBRTC: Local streams are visible on OPENVIDU WEB(other Participant) end But Remote Streams are coming NIL on my end locally swift iOS

I am using OpenVidu for Video Chat, Using this Repo https://github.com/OpenVidu/openvidu-ios-app I know this has bugs, but I have to use this as This is working fine at Android and WEB. I am able to make it working where my Local video can be seen on OpenVidu Web but Remote or other person who has joined session from Web whose video(Video Stream and Audio Stream) is not coming at my end. however I can see Remote Participant ID and Name at my end when a user joined the session.
Attached Image is the screen shot showing Remote streams are nil.
Below is the WebSocketListener Class I am using, I have updated pods so have to update delegates as well.
//
// WebSocketListener.swift
// WebRTCapp
//
// Created by Sergio Paniego Blanco on 01/05/2018.
// Copyright © 2018 Sergio Paniego Blanco. All rights reserved.
//
import Foundation
import Starscream
import WebRTC
class WebSocketListener: WebSocketDelegate {
let JSON_RPCVERSION = "2.0"
let useSSL = true
var socket: WebSocket
var helloWorldTimer: Timer?
var id = 0
var url: String
var sessionName: String
var participantName: String
var localOfferParams: [String: String]?
var iceCandidatesParams: [[String:String]]?
var userId: String?
var remoteParticipantId: String?
var participants: [String: RemoteParticipant]
var localPeer: RTCPeerConnection?
var peersManager: PeersManager
var token: String
var views: [UIView]!
var names: [UILabel]!
var key: String?
init(url: String, sessionName: String, participantName: String, peersManager: PeersManager, token: String, views: [UIView], names: [UILabel]) {
self.url = url
self.sessionName = sessionName
self.participantName = participantName
self.participants = [String: RemoteParticipant]()
self.peersManager = peersManager
self.token = token
var request = URLRequest(url: URL(string: url)!)
request.timeoutInterval = 20
socket = WebSocket(request: request)
socket.delegate = self
socket.connect()
self.localPeer = self.peersManager.localPeer
self.iceCandidatesParams = []
self.views = views
self.names = names
}
// func websocketDidConnect(socket: WebSocketClient) {
// print("Connected")
// pingMessageHandler()
// var joinRoomParams: [String: String] = [:]
// joinRoomParams["recorder"] = "false"
// joinRoomParams["platform"] = "iOS"
// joinRoomParams[JSONConstants.Metadata] = "{\"clientData\": \"" + "iOSUser" + "\"}"
// joinRoomParams["secret"] = "MY_SECRET"
// joinRoomParams["session"] = sessionName
// joinRoomParams["token"] = token
// sendJson(method: "joinRoom", params: joinRoomParams)
// if localOfferParams != nil {
// sendJson(method: "publishVideo",params: localOfferParams!)
// }
// }
func pingMessageHandler() {
helloWorldTimer = Timer.scheduledTimer(timeInterval: 5, target: self, selector: #selector(WebSocketListener.doPing), userInfo: nil, repeats: true)
doPing()
}
#objc func doPing() {
var pingParams: [String: String] = [:]
pingParams["interval"] = "5000"
sendJson(method: "ping", params: pingParams)
socket.write(ping: Data())
}
var isConnected = false
func websocketDidDisconnect(socket: WebSocketClient, error: Error?) {
print("Disconnect: " + error.debugDescription)
}
func didReceive(event: WebSocketEvent, client: WebSocket) {
switch event {
case .connected(let headers):
isConnected = true
print("websocket is connected: \(headers)")
pingMessageHandler()
var joinRoomParams: [String: String] = [:]
joinRoomParams["recorder"] = "false"
joinRoomParams["platform"] = "iOS"
joinRoomParams[JSONConstants.Metadata] = "{\"clientData\": \"\(self.participantName)\"}"
joinRoomParams["secret"] = ""
joinRoomParams["session"] = sessionName
joinRoomParams["token"] = token
sendJson(method: "joinRoom", params: joinRoomParams)
if localOfferParams != nil {
sendJson(method: "publishVideo",params: localOfferParams!)
}
case .disconnected(let reason, let code):
isConnected = false
print("websocket is disconnected: \(reason) with code: \(code)")
case .text(let string):
print("Received text: \(string)")
let data = string.data(using: .utf8)!
do {
let json: [String: Any] = try JSONSerialization.jsonObject(with: data, options : .allowFragments) as! [String : Any]
if json[JSONConstants.Result] != nil {
handleResult(json: json)
} else {
handleMethod(json: json)
}
} catch let error as NSError {
print("ERROR parsing JSON: ", error)
}
case .binary(let data):
print("Received data: \(data.count)")
case .ping(_):
break
case .pong(_):
break
case .viabilityChanged(_):
break
case .reconnectSuggested(_):
break
case .cancelled:
isConnected = false
case .error(let error):
isConnected = false
print(error.debugDescription)
}
}
// func websocketDidReceiveMessage(socket: WebSocketClient, text: String) {
// print("Recieved message: " + text)
// let data = text.data(using: .utf8)!
// do {
// let json: [String: Any] = try JSONSerialization.jsonObject(with: data, options : .allowFragments) as! [String : Any]
//
// if json[JSONConstants.Result] != nil {
// handleResult(json: json)
// } else {
// handleMethod(json: json)
// }
//
// } catch let error as NSError {
// print("ERROR parsing JSON: ", error)
// }
// }
func handleResult(json: [String: Any]) {
let result: [String: Any] = json[JSONConstants.Result] as! [String: Any]
if result[JSONConstants.SdpAnswer] != nil {
saveAnswer(json: result)
} else if result[JSONConstants.SessionId] != nil {
if result[JSONConstants.Value] != nil {
let value = result[JSONConstants.Value] as! [[String:Any]]
if !value.isEmpty {
addParticipantsAlreadyInRoom(result: result)
}
self.userId = result[JSONConstants.Id] as? String
for var iceCandidate in iceCandidatesParams! {
iceCandidate["endpointName"] = self.userId
sendJson(method: "onIceCandidate", params: iceCandidate)
}
}
} else if result[JSONConstants.Value] != nil {
print("pong")
} else {
print("Unrecognized")
}
}
func addParticipantsAlreadyInRoom(result: [String: Any]) {
let values = result[JSONConstants.Value] as! [[String: Any]]
for participant in values {
print(participant[JSONConstants.Id]!)
self.remoteParticipantId = participant[JSONConstants.Id]! as? String
let remoteParticipant = RemoteParticipant()
remoteParticipant.id = participant[JSONConstants.Id] as? String
let metadataString = participant[JSONConstants.Metadata] as! String
let data = metadataString.data(using: .utf8)!
do {
if let metadata = try JSONSerialization.jsonObject(with: data, options : .allowFragments) as? Dictionary<String,Any>
{
remoteParticipant.participantName = metadata["clientData"] as? String
}
} catch let error as NSError {
print(error)
}
self.participants[remoteParticipant.id!] = remoteParticipant
self.peersManager.createRemotePeerConnection(remoteParticipant: remoteParticipant)
let mandatoryConstraints = ["OfferToReceiveAudio": "true", "OfferToReceiveVideo": "true"]
let sdpConstraints = RTCMediaConstraints(mandatoryConstraints: mandatoryConstraints, optionalConstraints: nil)
remoteParticipant.peerConnection!.offer(for: sdpConstraints, completionHandler: {(sessionDescription, error) in
print("Remote Offer: " + error.debugDescription)
self.participants[remoteParticipant.id!]!.peerConnection!.setLocalDescription(sessionDescription!, completionHandler: {(error) in
print("Remote Peer Local Description set " + error.debugDescription)
})
var remoteOfferParams: [String:String] = [:]
remoteOfferParams["sdpOffer"] = sessionDescription!.sdp
remoteOfferParams["sender"] = self.remoteParticipantId! + "_CAMERA"
self.sendJson(method: "receiveVideoFrom", params: remoteOfferParams)
})
self.peersManager.remotePeer!.delegate = self.peersManager
}
}
func saveAnswer(json: [String:Any]) {
let sessionDescription = RTCSessionDescription(type: RTCSdpType.answer, sdp: json["sdpAnswer"] as! String)
if localPeer == nil {
self.localPeer = self.peersManager.localPeer
}
if (localPeer!.remoteDescription != nil) {
participants[remoteParticipantId!]!.peerConnection!.setRemoteDescription(sessionDescription, completionHandler: {(error) in
print("Remote Peer Remote Description set: " + error.debugDescription)
if self.peersManager.remoteStreams.count >= self.participants.count {
DispatchQueue.main.async {
print("Count: " + self.participants.count.description)
if UIDevice().userInterfaceIdiom == .phone && UIScreen.main.nativeBounds.height == 2436 {
let renderer = RTCEAGLVideoView(frame: self.views[self.participants.count-1].frame)
let videoTrack = self.peersManager.remoteStreams[self.participants.count-1].videoTracks[0]
videoTrack.add(renderer)
// Add the view and name to the first free space available
var index = 0
while (index < 2 && !(self.names[index].text?.isEmpty)!) {
index += 1
}
if index < 2 {
self.names[index].text = self.participants[self.remoteParticipantId!]?.participantName
self.names[index].backgroundColor = UIColor.black
self.names[index].textColor = UIColor.white
self.embedView(renderer, into: self.views[index])
self.participants[self.remoteParticipantId!]?.index = index
self.views[index].bringSubview(toFront: self.names[index])
}
}else
{
#if arch(arm64)
let renderer = RTCMTLVideoView(frame: self.views[self.participants.count-1].frame)
#else
let renderer = RTCEAGLVideoView(frame: self.views[self.participants.count-1].frame)
#endif
let videoTrack = self.peersManager.remoteStreams[self.participants.count-1].videoTracks[0]
videoTrack.add(renderer)
// Add the view and name to the first free space available
var index = 0
while (index < 2 && !(self.names[index].text?.isEmpty)!) {
index += 1
}
if index < 2 {
self.names[index].text = self.participants[self.remoteParticipantId!]?.participantName
self.names[index].backgroundColor = UIColor.black
self.names[index].textColor = UIColor.white
self.embedView(renderer, into: self.views[index])
self.participants[self.remoteParticipantId!]?.index = index
self.views[index].bringSubview(toFront: self.names[index])
}
}
}
}
})
} else {
localPeer!.setRemoteDescription(sessionDescription, completionHandler: {(error) in
print("Local Peer Remote Description set: " + error.debugDescription)
})
}
}
func handleMethod(json: Dictionary<String,Any>) {
if json[JSONConstants.Params] != nil {
let method = json[JSONConstants.Method] as! String
let params = json[JSONConstants.Params] as! Dictionary<String, Any>
switch method {
case JSONConstants.IceCandidate:
iceCandidateMethod(params: params)
case JSONConstants.ParticipantJoined:
participantJoinedMethod(params: params)
case JSONConstants.ParticipantPublished:
participantPublished(params: params)
case JSONConstants.ParticipantLeft:
participantLeft(params: params)
default:
print("Error handleMethod, " + "method '" + method + "' is not implemented")
}
}
}
func iceCandidateMethod(params: Dictionary<String, Any>) {
// if (params["endpointName"] as? String == userId) {
// saveIceCandidate(json: params, endPointName: nil)
// } else {
// saveIceCandidate(json: params, endPointName: params["endpointName"] as? String)
// }
DispatchQueue.main.async {
if params["senderConnectionId"] != nil {
self.key = "senderConnectionId"
} else {
self.key = "endpointName"
}
if (params[self.key ?? ""] as? String == self.userId) {
self.saveIceCandidate(json: params, endPointName: params["endpointName"] as? String)
} else {
self.saveIceCandidate(json: params, endPointName: params[self.key ?? ""] as? String)
}
}
}
// func websocketDidReceiveData(socket: WebSocketClient, data: Data) {
// print("Received data: " + data.description)
// }
func participantJoinedMethod(params: Dictionary<String, Any>) {
let remoteParticipant = RemoteParticipant()
remoteParticipant.id = params[JSONConstants.Id] as? String
self.participants[params[JSONConstants.Id] as! String] = remoteParticipant
let metadataString = params[JSONConstants.Metadata] as! String
let data = metadataString.data(using: .utf8)!
do {
if let metadata = try JSONSerialization.jsonObject(with: data, options : .allowFragments) as? Dictionary<String,Any>
{
remoteParticipant.participantName = metadata["clientData"] as? String
self.peersManager.createRemotePeerConnection(remoteParticipant: remoteParticipant)
} else {
print("bad json")
}
} catch let error as NSError {
print(error)
}
participantPublished(params: params)
}
func participantPublished(params: Dictionary<String, Any>) {
self.remoteParticipantId = params[JSONConstants.Id] as? String
print("ID: " + remoteParticipantId!)
let remoteParticipantPublished = participants[remoteParticipantId!]!
let mandatoryConstraints = ["OfferToReceiveAudio": "true", "OfferToReceiveVideo": "true"]
remoteParticipantPublished.peerConnection!.offer(for: RTCMediaConstraints.init(mandatoryConstraints: mandatoryConstraints, optionalConstraints: nil), completionHandler: { (sessionDescription, error) in
remoteParticipantPublished.peerConnection!.setLocalDescription(sessionDescription!, completionHandler: {(error) in
print("Remote Peer Local Description set")
})
var remoteOfferParams: [String: String] = [:]
remoteOfferParams["sdpOffer"] = sessionDescription!.description
remoteOfferParams["sender"] = remoteParticipantPublished.id! + "_webcam"
self.sendJson(method: "receiveVideoFrom", params: remoteOfferParams)
})
self.peersManager.remotePeer!.delegate = self.peersManager
}
func participantLeft(params: Dictionary<String, Any>) {
print("participants", participants)
print("params", params)
let participantId = params["connectionId"] as! String
participants[participantId]!.peerConnection!.close()
if UIDevice().userInterfaceIdiom == .phone && UIScreen.main.nativeBounds.height == 2436 {
let renderer = RTCEAGLVideoView(frame: self.views[0].frame)
//REMOVE VIEW
if(self.peersManager.remoteStreams.count > 0){
let videoTrack = self.peersManager.remoteStreams[0].videoTracks[0]
videoTrack.remove(renderer)
if let index = self.participants.keys.index(of: participantId) {
let i = participants.distance(from: participants.startIndex, to: index)
self.views[i].willRemoveSubview(renderer)
self.names[i].text = ""
self.names[i].backgroundColor = UIColor.clear
}
}
participants.removeValue(forKey: participantId)
}else
{
#if arch(arm64)
let renderer = RTCMTLVideoView(frame: self.views[0].frame)
#else
let renderer = RTCEAGLVideoView(frame: self.views[self.participants.count-1].frame)
#endif
//REMOVE VIEW
if(self.peersManager.remoteStreams.count > 0){
let videoTrack = self.peersManager.remoteStreams[0].videoTracks[0]
videoTrack.remove(renderer)
if let index = self.participants.keys.index(of: participantId) {
let i = participants.distance(from: participants.startIndex, to: index)
self.views[i].willRemoveSubview(renderer)
self.names[i].text = ""
self.names[i].backgroundColor = UIColor.clear
}
participants.removeValue(forKey: participantId)
}
}
}
func saveIceCandidate(json: Dictionary<String, Any>, endPointName: String?) {
let iceCandidate = RTCIceCandidate(sdp: json["candidate"] as! String, sdpMLineIndex: json["sdpMLineIndex"] as! Int32, sdpMid: json["sdpMid"] as? String)
if (endPointName == nil || participants[endPointName!] == nil) {
self.localPeer = self.peersManager.localPeer
self.localPeer!.add(iceCandidate)
} else {
participants[endPointName!]!.peerConnection!.add(iceCandidate)
}
}
func sendJson(method: String, params: [String: String]) {
let json: NSMutableDictionary = NSMutableDictionary()
json.setValue(method, forKey: JSONConstants.Method)
json.setValue(id, forKey: JSONConstants.Id)
id += 1
json.setValue(params, forKey: JSONConstants.Params)
json.setValue(JSON_RPCVERSION, forKey: JSONConstants.JsonRPC)
let jsonData: NSData
do {
jsonData = try JSONSerialization.data(withJSONObject: json, options: JSONSerialization.WritingOptions()) as NSData
let jsonString = NSString(data: jsonData as Data, encoding: String.Encoding.utf8.rawValue)! as String
print("Sending = \(jsonString)")
socket.write(string: jsonString)
} catch _ {
print ("JSON Failure")
}
}
func addIceCandidate(iceCandidateParams: [String: String]) {
iceCandidatesParams!.append(iceCandidateParams)
}
func embedView(_ view: UIView, into containerView: UIView) {
containerView.addSubview(view)
containerView.backgroundColor = UIColor.white.withAlphaComponent(0.8)
view.centerXAnchor.constraint(equalTo: containerView.centerXAnchor).isActive = true
view.centerYAnchor.constraint(equalTo: containerView.centerYAnchor).isActive = true
}
}
Any One who has used this code, needed help here. I have Participant ID what is the way to get Audio Video streams, Do I need to make some connection(Peer connection)? or I will get these streams in the socket connection response only.

Swift firebase search is one reload behind

I have a search function in my app which works perfectly except for the fact that it seems to be one reload behind. For example if I type in 'S' nothing will show but if I then type an 'a' after all the results for 'S' will show up. I tried adding collectionView reload at the end of the fetch function but if I do that nothing shows up period.
#objc func textFieldDidChange(_ textField: UITextField) {
guard let text = textField.text else { return }
if text.count == 0 {
self.posts.removeAll()
self.filteredPosts.removeAll()
} else {
fetchSearchedPosts(searchTerm: text)
}
self.collectionView.reloadData()
}
func fetchSearchedPosts(searchTerm: String) {
self.collectionView.refreshControl?.endRefreshing()
let ref = Database.database().reference().child("posts").queryOrdered(byChild: "title").queryStarting(atValue: searchTerm).queryEnding(atValue: "\(searchTerm)\u{f8ff}")
ref.observeSingleEvent(of: .value) { (snapshot) in
if !snapshot.exists() { return }
guard let dictionaries = snapshot.value as? [String: Any] else { return }
self.posts.removeAll()
dictionaries.forEach({ (key, value) in
guard let postDictionary = value as? [String: Any] else { return }
guard let uid = postDictionary["uid"] as? String else { return }
Database.fetchUserWithUID(uid: uid, completion: { (user) in
let post = Post(postId: key, user: user, dictionary: postDictionary)
let nowTimeStamp = Date().timeIntervalSince1970
let dateTime = post.endTimeDate
let timeStamp = dateTime.timeIntervalSince1970
if nowTimeStamp < timeStamp {
post.id = key
self.posts.append(post)
} else {
return
}
})
self.posts.sort(by: { (post1, post2) -> Bool in
return post1.title.compare(post2.title) == .orderedAscending
})
})
self.collectionView.reloadData()
}
}
You need a DispatchGroup as you have nested asynchronous calls
func fetchSearchedPosts(searchTerm: String) {
self.collectionView.refreshControl?.endRefreshing()
let ref = Database.database().reference().child("posts").queryOrdered(byChild: "title").queryStarting(atValue: searchTerm).queryEnding(atValue: "\(searchTerm)\u{f8ff}")
ref.observeSingleEvent(of: .value) { (snapshot) in
if !snapshot.exists() { return }
guard let dictionaries = snapshot.value as? [String: Any] else { return }
self.posts.removeAll()
let g = DispatchGroup() ///// 1
dictionaries.forEach({ (key, value) in
guard let postDictionary = value as? [String: Any] else { return }
guard let uid = postDictionary["uid"] as? String else { return }
g.enter() ///// 2
Database.fetchUserWithUID(uid: uid, completion: { (user) in
let post = Post(postId: key, user: user, dictionary: postDictionary)
let nowTimeStamp = Date().timeIntervalSince1970
let dateTime = post.endTimeDate
let timeStamp = dateTime.timeIntervalSince1970
if nowTimeStamp < timeStamp {
post.id = key
self.posts.append(post)
} else {
g.leave() ///// 3.a
return
}
g.leave() ///// 3.b
})
})
g.notify(queue:.main) { ///// 4
self.posts.sort(by: { (post1, post2) -> Bool in
return post1.title.compare(post2.title) == .orderedAscending
})
self.collectionView.reloadData()
}
}
}

Can I use generics to cast NSNumber to T in swift?

What I want
func safeGet<T>() -> T {
let value = magic()
if let typedValue = value as? T {
return typedValue
}
}
The reason this doesn't work is the fact that you can't do <NSNumber> as Int in swift
What do I put in the <what do I put here?> placeholder?
func safeGet<T>() -> T {
let value = magic()
if let typedValue = value as? NSNumber {
return <what do I put here?>
} else if let typedValue = value as? T {
return typedValue
}
}
In the end I did this to be able to get typed values from the dictionary in a way that throws errors. It's used like this
let name:String = dictionary.safeGet("name")
or
let name = dictionary.safeGet("name") as String`
The source code:
import Foundation
extension Dictionary {
func safeGet<T>(key:Key) throws -> T {
if let value = self[key] as? AnyObject {
if let typedValue = value as? T {
return typedValue
}
let typedValue: T? = parseNumber(value)
if typedValue != nil {
return typedValue!
}
let typeData = Mirror(reflecting: value)
throw generateNSError(
domain: "DictionaryError.WrongType",
message: "Could not convert `\(key)` to `\(T.self)`, it was `\(typeData.subjectType)` and had the value `\(value)`"
)
} else {
throw generateNSError(
domain: "DictionaryError.MissingValue",
message: "`\(key)` was not in dictionary. The dictionary was:\n\(self.description)"
)
}
}
private func parseNumber<T>(value: AnyObject) -> T? {
if Int8.self == T.self {
if let numericValue = value as? NSNumber {
return numericValue.charValue as? T
} else if let stringValue = value as? String {
return Int8(stringValue) as? T
}
} else if Int16.self == T.self {
if let numericValue = value as? NSNumber {
return numericValue.shortValue as? T
} else if let stringValue = value as? String {
return Int16(stringValue) as? T
}
} else if Int32.self == T.self {
if let numericValue = value as? NSNumber {
return numericValue.longValue as? T
} else if let stringValue = value as? String {
return Int32(stringValue) as? T
}
} else if Int64.self == T.self {
if let numericValue = value as? NSNumber {
return numericValue.longLongValue as? T
} else if let stringValue = value as? String {
return Int64(stringValue) as? T
}
} else if UInt8.self == T.self {
if let numericValue = value as? NSNumber {
return numericValue.unsignedCharValue as? T
} else if let stringValue = value as? String {
return UInt8(stringValue) as? T
}
} else if UInt16.self == T.self {
if let numericValue = value as? NSNumber {
return numericValue.unsignedShortValue as? T
} else if let stringValue = value as? String {
return UInt16(stringValue) as? T
}
} else if UInt32.self == T.self {
if let numericValue = value as? NSNumber {
return numericValue.unsignedIntValue as? T
} else if let stringValue = value as? String {
return UInt32(stringValue) as? T
}
} else if UInt64.self == T.self {
if let numericValue = value as? NSNumber {
return numericValue.unsignedLongLongValue as? T
} else if let stringValue = value as? String {
return UInt64(stringValue) as? T
}
} else if Double.self == T.self {
if let numericValue = value as? NSNumber {
return numericValue.doubleValue as? T
} else if let stringValue = value as? String {
return Double(stringValue) as? T
}
} else if Float.self == T.self {
if let numericValue = value as? NSNumber {
return numericValue.floatValue as? T
} else if let stringValue = value as? String {
return Float(stringValue) as? T
}
} else if String.self == T.self {
if let numericValue = value as? NSNumber {
return numericValue.stringValue as? T
} else if let stringValue = value as? String {
return stringValue as? T
}
}
return nil
}
private func generateNSError(domain domain: String, message: String) -> NSError {
return NSError(
domain: domain,
code: -1,
userInfo: [
NSLocalizedDescriptionKey: message
])
}
}