Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

iUsmanN/CrowdCast_iOS

Repository files navigation

----------------------

Crowd Cast is designed to be a modern video calling and management platform. With Crowdcast, there is no need to individually share video call links with people anymore. Providing an independant video calling platform, Crowd Cast allows you to create and join video call channels seamlessly. Crowds are groups of people that can be created where each member can access each video call channel within the Crowd. The individual channels you create can be shared and join using generated short deeplinks that provide a swift experience to quickly get started.


Some Screens

Crowd Cast is a side project aimed at showcasing my programming style and some of the technologies that I have worked with over time with Swift in iOS.

The app includes the following programming practices (Examples attached):

  • MVVM

  • Protocol Oriented Programming
protocol CCSetsNavbar : CCOpensSettings {}
extension CCSetsNavbar {
 
 /// Sets up navigation bar
 /// - Parameters:
 /// - navigationBar: View Controller's navigation bar
 /// - navigationItem: View Controller's navigation controller
 /// - title: View Controller's title
 /// - largeTitles: should prefer large titles
 /// - profileAction: profile button action
 func setupNavBar(navigationBar: UINavigationBar?,navigationItem: UINavigationItem, title: String?, largeTitles: Bool, profileAction: Selector?){
 navigationItem.title = title
 navigationBar?.prefersLargeTitles = largeTitles
 let leftButton = getLogoButton()
 let profileButton = getProfileButton(action: profileAction)
 profileButton.action = profileAction
 navigationItem.leftBarButtonItem = leftButton
 navigationItem.setRightBarButtonItems([profileButton], animated: true)
 }
}
  • Generic Programming
func request<T: Codable>(endpoint: Endpoint, result: @escaping (Result<T, CCError>)->()){
 var components = URLComponents()
 components.scheme = endpoint.scheme
 components.host = endpoint.host
 components.path = endpoint.path
 
 guard let url = components.url else { return }
 var urlRequest = URLRequest(url: url)
 urlRequest.httpMethod = endpoint.httpMethod.rawValue
 
 let session = URLSession(configuration: .default)
 let dataTask = session.dataTask(with: urlRequest) { data, response, error in
 guard error == nil, response != nil, let data = data else { return }
 let responseObject = try! JSONDecoder().decode(T.self, from: data)
 result(.success(responseObject))
 }
 dataTask.resume()
 }
  • Multithreading

Defining custom Dispatch Queue protocol for ease of use:

protocol CCDispatchQueue {}
extension CCDispatchQueue {
 
 /// Dispatch Priority Item
 /// - Parameters:
 /// - type: Async/Sync
 /// - code: Code Block to run
 /// - Returns: nil
 func dispatchPriorityItem(_ type: DispatchQueue.Attributes, code: @escaping ()->()){
 let queue = DispatchQueue(label: "com.CrowdCast.HighPriority", qos: .utility, attributes: type)
 queue.async(execute: DispatchWorkItem(block: code))
 }
}

Implementation:

extension CCChannelsVM : CCChannelsService, CCDispatchQueue, CCGetIndexPaths {
 
 func fetchFreshData() {
 
 let dg = DispatchGroup()
 
 var fetchedCounts = (0, 0)
 var newMyChannels = 0
 var newJoinedChannels = 0
 
 dg.enter()
 dispatchPriorityItem(.concurrent, code: {
 self.getUserChannels(type: .owned) { (result) in
 switch result {
 case .success(let inputData):
 self.myChannels.clearData()
 self.myChannels.updateData(input: inputData)
 fetchedCounts = (inputData.data.count, fetchedCounts.1)
 newMyChannels = inputData.data.count
 dg.leave()
 case .failure(let error):
 prints(error)
 dg.leave()
 }
 }
 })
 
 dg.enter()
 dispatchPriorityItem(.concurrent, code: {
 self.getUserChannels(type: .joined) { (result) in
 switch result {
 case .success(let inputData):
 self.joinedChannels.clearData()
 self.joinedChannels.updateData(input: inputData)
 fetchedCounts = (fetchedCounts.0, inputData.data.count)
 newJoinedChannels = inputData.data.count
 dg.leave()
 case .failure(let error):
 prints(error)
 dg.leave()
 }
 }
 })
 
 dg.notify(queue: .global()) { [weak self] in
 self?.publishChannelUpdates(action: CCChannelsVC.refresh ? .refresh : .insert, newCreatedChannels: newMyChannels, newJoinedChannels: newJoinedChannels)
 CCChannelsVC.refresh = false
 }
 }
  • Swift Combine
class CCChannelsVM {
 .
 .
 .
 let channelsPublisher = PassthroughSubject<(dataAction, [IndexPath]), Never>()
 .
 .
 .
 self?.publishChannelUpdates(action: CCChannelsVC.refresh ? .refresh : .insert, newCreatedChannels: newMyChannels, newJoinedChannels: newJoinedChannels)
 .
 .
 .
}
extension CCChannelsVC {
 
 func bindVM(){
 viewModel?.channelsPublisher.sink(receiveValue: { [weak self] (indexPathsInput) in
 switch indexPathsInput.0 {
 case .insert:
 self?.insertRows(at: indexPathsInput.1)
 case .remove:
 self?.removeRows(at: indexPathsInput.1)
 case .refresh:
 self?.refreshRows()
 }}).store(in: &combineCancellable)
 }
 .
 .
 .
}
  • Swift Extensions
class CCCameraView : UIView {
 
 var captureSession = AVCaptureSession()
 var videoPreviewLayer : AVCaptureVideoPreviewLayer?
 var capturePhotoOutput = AVCapturePhotoOutput()
 
 func setupCameraView() {
 initUI(.front)
 }
 
 func initUI(_ position: AVCaptureDevice.Position) {
 guard let captureDevice = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: position) else { return }
 do {
 let input = try AVCaptureDeviceInput(device: captureDevice)
 if captureSession.canAddInput(input) { captureSession.addInput(input) }
 DispatchQueue.main.async { [weak self] in
 self?.videoPreviewLayer = AVCaptureVideoPreviewLayer(session: self!.captureSession)
 self?.videoPreviewLayer?.videoGravity = AVLayerVideoGravity.resizeAspectFill
 self?.videoPreviewLayer?.frame = self!.frame
 self?.layer.addSublayer(self!.videoPreviewLayer!)
 self?.captureSession.commitConfiguration()
 }
 } catch {
 print("Error")
 }
 }
}
  • Swift Enums
enum CardHeaderAction {
 case newChannel, newGroup, joinChannel, joinGroup, viewAll, pinnedChannels
}

Frameworks used:

  • Firestore

//MARK: CHANNELS
extension CCQueryEngine {
 .
 .
 .
 func userChannel(id: String?) -> DocumentReference {
 let db = Firestore.firestore()
 let env = Constants.environment
 return db.document("\(env)\(CCQueryPath.channelsData.rawValue)/\(id ?? "")")
 }
 .
 .
 .
}
func fetchData<T: Codable>(query: Query, completion: @escaping (Result<[T], Error>) -> ()){
 query.getDocuments { (documents, error) in
 guard error == nil, let data = documents else {
 completion(.failure(CCError.firebaseFailure))
 return
 }
 do {
 let output = try data.documents.compactMap({ try 0ドル.data(as: T.self) })
 completion(.success(output))
 } catch {
 completion(.failure(CCError.networkEngineFailure))
 }
 }
 }
}
  • Twilio Video SDK

extension CCCallScreenVM : CCTwilioService {
 
 ///Joins the Twilio Video Channel
 func joinChannel(result: ((Result<Room, CCError>)->())?){
 guard let channelID = channelData?.id else { result?(.failure(.twilioCredentialsError)); return }
 refreshAccessToken { [weak self](tokenResult) in
 switch tokenResult {
 case .success(let token):
 let connectOptions = ConnectOptions(token: token.token ?? "") { (connectOptionsBuilder) in
 connectOptionsBuilder.roomName = channelID
 if let audioTrack = self?.localAudioTrack {
 connectOptionsBuilder.audioTracks = [ audioTrack ]
 }
 if let dataTrack = self?.localDataTrack {
 connectOptionsBuilder.dataTracks = [ dataTrack ]
 }
 if let videoTrack = self?.localVideoTrack {
 connectOptionsBuilder.videoTracks = [ videoTrack ]
 }
 }
 self?.room = TwilioVideoSDK.connect(options: connectOptions, delegate: self)
 case .failure(let error):
 result?(.failure(error))
 }
 }
 }
}
  • Firebase Deeplinking

protocol CCDynamicLinkEngine {}
extension CCDynamicLinkEngine {
 
 func generateShareLink<T: CCContainsID>(input: T?, completion: @escaping (Result<String?, CCError>)->()){
 let lp = linkProperties()
 lp.addControlParam("id", withValue: input?.id ?? "")
 lp.addControlParam("isGroup", withValue: input is CCChannel ? "false" : "true")
 universalObject().getShortUrl(with: lp) { (string, error) in
 guard error == nil else { completion(.failure(.branchLinkError)); return }
 completion(.success(string))
 }
 }
}
  • Bulletin Board
class CCBulletinManager {
 
 var manager: BLTNItemManager?
 
 func setItem(item: BLTNItem) {
 manager = BLTNItemManager(rootItem: item)
 manager?.backgroundViewStyle = .dimmed
 }
 
 static func joinChannel() -> BLTNPageItem {
 let page = CCBLTNPageItem(title: "Join Channel")
 page.alternativeButtonTitle = "Join via Dynamic Link"
 page.alternativeHandler = { item in
 page.next = enterCode()
 item.manager?.displayNextItem()
 }
 return page
 }
 .
 .
 .
}

Muhammad Usman Nazir

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 2

AltStyle によって変換されたページ (->オリジナル) /