Problem:
UIView updates cause MainViewController to freeze until UI changes are updated. Can I avoid the freezing of the MainViewController using dispatchAsync or is this completely set up wrong?
I have a MainViewController that has a UIView connected as an IBOutlet via storyboard.
class MainViewController: UITableViewController {
#IBOutlet weak var headerView: MettaHeaderView!
func loadCustomView() {
DispatchQueue.main.async {
self.headerView.populateViews()
}
}
}
The UIView Class is set up like this
class MettaHeaderView: UIView {
//MARK: - Properties
weak var delegate: MettaTableVC!
let dataStore = DataStore.shared
//MARK: - Outlets
#IBOutlet weak var cardView: CurvedUIView!
#IBOutlet weak var dayStack: UIStackView!
#IBOutlet weak var plusBtn: UIButton!
#IBOutlet weak var segmentedControl: UISegmentedControl!
#IBOutlet weak var streakLbl: UILabel!
#IBOutlet weak var todayImg: UIImageView!
#IBOutlet weak var todayLbl: UILabel!
#IBOutlet weak var yesterdayImg: UIImageView!
#IBOutlet weak var yesterdayLbl: UILabel!
#IBOutlet weak var twoDayAgoImg: UIImageView!
#IBOutlet weak var twoDayAgoLbl: UILabel!
#IBOutlet weak var threeDayAgoImg: UIImageView!
#IBOutlet weak var threeDayAgoLbl: UILabel!
#IBOutlet weak var fourDayAgoImg: UIImageView!
#IBOutlet weak var fourDayAgoLbl: UILabel!
#IBOutlet weak var fiveDayAgoImg: UIImageView!
#IBOutlet weak var fiveDayAgoLbl: UILabel!
#IBOutlet weak var sixDayAgoImg: UIImageView!
#IBOutlet weak var sixDayAgoLbl: UILabel!
//MARK: - Override Methods
override func layoutMarginsDidChange() {
super.layoutMarginsDidChange()
self.layoutIfNeeded()
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
self.endEditing(true)
}
//MARK: - Methods
func initializeHeader(with delegate: MettaTableVC) {
self.delegate = delegate
styleCardView()
let font = UIFont.systemFont(ofSize: 12)
let selectedFont = UIFont.systemFont(ofSize: 12, weight: .medium)
segmentedControl.setTitleTextAttributes([NSAttributedString.Key.font: font],
for: .normal)
segmentedControl.setTitleTextAttributes([NSAttributedString.Key.font: selectedFont],
for: .selected)
}
func styleCardView() {
self.layoutIfNeeded()
self.layoutSubviews()
cardView.curvedPercent = 0.17
setFontSize()
}
func populateViews() {
let totalDays = self.totalDays()
let dayString = totalDays == 1 ? "Day" : "Days"
self.streakLbl.text = "\(totalDays) \(dayString) with a Session"
self.setDayLbls()
self.setDayImages(with: dataStore.sessions)
}
func totalDays() -> Int {
let grouped = dataStore.sessions.group { $0.dateString }
return grouped.count
}
func setStreak(with sessions: [MeditationSession]) {
streakLbl.text = "\(currentStreak(from: sessions)) day streak"
}
func currentStreak(from sessions: [MeditationSession]) -> Int {
var streak = 0
var lastDate: Date?
let formatter = DateFormatter()
formatter.dateFormat = "MM/dd/yyyy"
for sesh in sessions {
let date = sesh.date.dateValue()
// Check if first day check complete yet
if let last = lastDate {
let difference = daysBetween(start: date, end: last)
// Check if the same day
if difference == 0 {
continue
}
// Check if day before
if difference == 1 {
lastDate = date
streak += 1
} else {
return streak
}
// If first go through
} else {
let difference = daysBetween(start: date, end: Date())
// Check if today
if difference == 0 {
lastDate = date
streak += 1
continue
}
// Check if day before
if difference == 1 {
lastDate = date
streak += 1
} else {
return streak
}
}
}
return streak
}
func daysBetween(start: Date, end: Date) -> Int {
let cal = Calendar(identifier: .gregorian)
let date1 = cal.startOfDay(for: start)
let date2 = cal.startOfDay(for: end)
return Calendar.current.dateComponents([.day], from: date1, to: date2).day!
}
func setDayLbls() {
let lbls: [UILabel] = [sixDayAgoLbl, fiveDayAgoLbl, fourDayAgoLbl, threeDayAgoLbl, twoDayAgoLbl, yesterdayLbl, todayLbl]
let formatter = DateFormatter()
formatter.dateFormat = "EEEE"
let todayString = formatter.string(from: Date())
guard let today = Day(rawValue: todayString.lowercased()) else { return }
let week = past7Days(from: today)
for (index, val) in week.enumerated() {
lbls[index].text = val
}
}
func setFontSize() {
let labels: [UILabel] = [sixDayAgoLbl, fiveDayAgoLbl, fourDayAgoLbl, threeDayAgoLbl, twoDayAgoLbl, yesterdayLbl, todayLbl]
labels.forEach {
let size: CGFloat = ($0.superview?.frame.width)! * 0.42
$0.font = $0.font.withSize(size)
}
}
func past7Days(from day: Day) -> [String] {
var dayStrings: [String] = []
var currentIndex: Int = day.order
let days = Day.allCasesOrdered
for _ in 0..<7 {
print("here")
let day = days[currentIndex]
dayStrings.append(day.letter)
if currentIndex > 0 {
currentIndex -= 1
} else {
currentIndex = 6
}
}
return dayStrings.reversed()
}
func setDayImages(with sessions: [MeditationSession]) {
let dayImgViews: [UIImageView] = [todayImg, yesterdayImg, twoDayAgoImg, threeDayAgoImg, fourDayAgoImg, fiveDayAgoImg, sixDayAgoImg]
var date: Date = Date()
for index in 0...6 {
print(index)
let filtered = sessions.filter { $0.dateString == date.dateString() }
dayImgViews[index].image = filtered.count == 0 ? #imageLiteral(resourceName: "Lotus_Monochromatic") : #imageLiteral(resourceName: "Lotus")
date = Calendar.current.date(byAdding: .day, value: -1, to: date)!
}
}
Calling the function below in the MainViewController freezes the entire VC until the function completes. Is there a way I can avoid this or fix my problem?
self.headerView.populateViews()
Freeze:
Meaning the entire screen is frozen until that function completes.
So I have found that the issue is because of this function but im not sure why the freeze is happening:
func setDayImages(with sessions: [MeditationSession]) {
let dayImgViews: [UIImageView] = [headerView.todayImg, headerView.yesterdayImg, headerView.twoDayAgoImg, headerView.threeDayAgoImg, headerView.fourDayAgoImg, headerView.fiveDayAgoImg, headerView.sixDayAgoImg]
var date: Date = Date()
for index in 0...6 {
print(index)
let filtered = sessions.filter { $0.dateString == date.dateString() }
dayImgViews[index].image = filtered.count == 0 ? #imageLiteral(resourceName: "Lotus_Monochromatic") : #imageLiteral(resourceName: "Lotus")
date = Calendar.current.date(byAdding: .day, value: -1, to: date)!
}
}
The function outlined at the end func setDayImages(with sessions: [MeditationSession]) inserts images inside UIImageViews, which means each image needs to be loaded into memory as a Bitmap, this is quite a heavy operation and you're running it on the Main thread in your MainViewController.
My solution would be to load the images into UIImage() outside of the main thread and then assign them on the main thread later. And see if that improves performance. And in general it is advised that calculations unrelated to the UI be performed outside of the main thread using GCD (DispatchQueue.global(.userInitiated).async for example).
This is my best guess at least without additional performance diagnostics.
Related
here is all of the code that I am using for this project. I have four progress views and when the first end an alarm goes off and then the next start eminently and then goes to the next and then to the fourth one where the timer ends after that and the timer goes off one more time and then the timers stop.
import UIKit
import AVFoundation
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
#IBAction func btnPressed4(_ sender: Any) {
let currentDateTime = Date()
let formatter = DateFormatter()
formatter.timeStyle = .short
let dateTimeString = formatter.string(from: currentDateTime)
timePrint4.text = dateTimeString
btnPressed4.titleLabel?.textColor = UIColor.white
}
#IBAction func btnPressed3(_ sender: Any) {
let currentDateTime = Date()
let formatter = DateFormatter()
formatter.timeStyle = .short
let dateTimeString = formatter.string(from: currentDateTime)
timePrint3.text = dateTimeString
btnPressed3.titleLabel?.textColor = UIColor.white
}
#IBAction func btnPressed2(_ sender: UIButton) {
let currentDateTime = Date()
let formatter = DateFormatter()
formatter.timeStyle = .short
let dateTimeString = formatter.string(from: currentDateTime)
timePrint2.text = dateTimeString
btnPressed2.titleLabel?.textColor = UIColor.green
}
#IBAction func btnPressed1(_ sender: UIButton) {
let currentDateTime = Date()
let formatter = DateFormatter()
formatter.timeStyle = .short
let dateTimeString = formatter.string(from: currentDateTime)
timePrint1.text = dateTimeString
}
#IBOutlet weak var btnPressed1: UIButton!
#IBOutlet weak var btnPressed2: UIButton!
#IBOutlet weak var btnPressed3: UIButton!
#IBOutlet weak var btnPressed4: UIButton!
#IBOutlet weak var timePrint1: UILabel!
#IBOutlet weak var timePrint2: UILabel!
#IBOutlet weak var timePrint4: UILabel!
#IBOutlet weak var timePrint3: UILabel!
#IBOutlet weak var titleLabel: UILabel!
#IBOutlet weak var progressBar1: UIProgressView!
#IBOutlet weak var progressBar2: UIProgressView!
#IBOutlet weak var progressBar3: UIProgressView!
#IBOutlet weak var progressBar4: UIProgressView!
let start = 5
var timer = Timer()
var player: AVAudioPlayer!
var totalTime = 0
var secondsPassed = 0
#IBAction func startButtonPressed(_ sender: UIButton) {
let startB = sender.titleLabel?.text
totalTime = start
progressBar1.progress = 0.0
secondsPassed = 0
titleLabel.text = "coffee timer"
btnPressed1.titleLabel?.textColor = UIColor.white
timer = Timer.scheduledTimer(timeInterval: 1.0, target:self, selector: #selector(updateTimer), userInfo:nil, repeats: true)
}
#objc func updateTimer() {
if secondsPassed < totalTime {
secondsPassed += 1
progressBar1.progress = Float(secondsPassed) / Float(totalTime)
print(Float(secondsPassed) / Float(totalTime))
} else {
timer.invalidate()
titleLabel.text = "check coffee"
btnPressed1.titleLabel?.textColor = UIColor.green
let url = Bundle.main.url(forResource: "alarm_sound", withExtension: "mp3")
player = try! AVAudioPlayer(contentsOf: url!)
player.play()
}
}
}
There's lots of different ways to handle this. Here is an outline of a possible approach:
Have an instance variable currentProgress, of type Int? If it's nil, none of your progress indicators is running.
Have an instance var stepCompletedInterval: Double
Create a struct to hold the different views you use to manage a progress step:
struct ProgressInfo {
let aButton: UIButton
let aLabel: UILabel
let aProgressIndicator: UIProgressIndicator
let secondsForThisStep: Int
}
Create an array of ProgressInfo, (let's call it progressInfoArray) and populate it with the buttons, labels, and progress indicators you have above. Also give each entry a value for secondsForThisStep.
When the user starts the process, set currentProgress to 0 (The first progress indicator.)
Now, for each step, set a stepCompletedInterval to the current time plus progressInfoArray[currentProgress].secondsForThisStep (The time at which the current step should complete.
Also start a 1-second repeating timer. Each time the timer fires, check to see if the current time is greater than stepCompletedInterval. If it is, increment currentProgress if there are still more steps to complete, and repeat the "now for each step" part above.
That is a rough outline of how you might go about it. I'm not completely clear on what your start buttons and btnPressed1 to btnPressed4 are supposed to do, so I'll leave that for you.
You'll need to adapt the approach I've outlined to your needs. It isn't code, it's an approach. I'm not going to give you code.
Heres the code I have so far, now when a user inputs any letter, my label display nothing, what I would like to figure out is how to turn that nothing "", into a 0. I tried doing an if statement on my "label.txt ="'s but that didn't pan out. What would be a better way of finding my desired results?
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var game1: UITextField!
#IBOutlet weak var game2: UITextField!
#IBOutlet weak var game3: UITextField!
#IBOutlet weak var series: UILabel!
#IBOutlet weak var average: UILabel!
#IBOutlet weak var high: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
#IBAction func calculate(_ sender: Any) {
self.view.endEditing(true)
guard
let text1 = game1.text,
let text2 = game2.text,
let text3 = game3.text
else { return }
guard
let game1Results = Int(text1),
let game2Results = Int(text2),
let game3Results = Int(text3)
else { return }
let gameResultsArray = [game1Results, game2Results, game3Results]
let sumArray = gameResultsArray.reduce(0, +)
let positiveArray = gameResultsArray.filter {
(item: Int) -> Bool in return item > 0
}
var avgArrayValue = 0
if positiveArray.count == 0
{
avgArrayValue = 0
}else {
avgArrayValue = sumArray / positiveArray.count
}
series.text = "\(sumArray)"
average.text = "\(avgArrayValue)"
if let maximumVal = gameResultsArray.max() {
high.text = String(maximumVal)
}
}
}
Here is what you need, convert String to Int and give the default 0. Instead of using the guard let return use this method:
Instead of this:
guard let game1Results = Int(text1) else { return }
Use this:
let game1Results = Int(text1) ?? 0
so I'm trying to find the average of my results using only user inputed values above 0. I'm not sure how I would check the results in the array to find out which values are above 0 and exclude them from the calculation in swift.
class ViewController: UIViewController {
#IBOutlet weak var game1: UITextField!
#IBOutlet weak var game2: UITextField!
#IBOutlet weak var game3: UITextField!
#IBOutlet weak var series: UILabel!
#IBOutlet weak var average: UILabel!
#IBOutlet weak var high: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
#IBAction func calculate(_ sender: Any) {
self.view.endEditing(true)
guard
let text1 = game1.text,
let text2 = game2.text,
let text3 = game3.text
else { return }
guard
let game1Results = Int(text1),
let game2Results = Int(text2),
let game3Results = Int(text3)
else { return }
let gameResultsArray = [game1Results, game2Results, game3Results]
let sumArray = gameResultsArray.reduce(0, +)
let avgArrayValue = sumArray / gameResultsArray.count
series.text = "\(sumArray)"
average.text = "\(avgArrayValue)"
if let maximumVal = gameResultsArray.max() {
high.text = String(maximumVal)
}
}
}
To filter out all values above 0 in an array, use the filter method. This will return a subset of your original array with all values passing the condition declared inside -
var demoArray = [-1, -8, 0, 1, 5 ,6]
var positiveArray = demoArray.filter { (item: Int) -> Bool in
return item > 0
}
print(positiveArray) // [1, 5, 6]
simply you can use filter
..
let sumArray = gameResultsArray.filter({$0 > 0})
let avgArrayValue = sumArray.reduce(0, +) / sumArray.count
when the user gets from CallKit at that time I am switching root on accept button click of Call but somehow root controller object always found nil and application crashed
Case : this is happing when the application running in the background and phone state is locked.
func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {
endCallTimer()
guard let call = callManager.callWithUUID(uuid: action.callUUID) else {
action.fail()
return
}
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let incomingCall = mainStoryboard.instantiateViewController(withIdentifier: "CallConnectedVC") as! IncomingController
incomingCall.connectToCalling(duration:self.callDict["duration"] as! Int, consumerId: "\(self.callDict["consumerId"] as! Int)", categoryName: self.callDict["categoryTopic"] as! String, categoryImage: "", callId: self.callDict["callId"] as! String)
let nav = UINavigationController(rootViewController: incomingCall)
UIApplication.shared.keyWindow?.makeKeyAndVisible()
UIApplication.shared.keyWindow?.rootViewController = nav
configureAudioSession()
call.answer()
action.fulfill()
}
IncomingController code -
import UIKit
import TwilioVideo
import AVFoundation
import SDWebImage
import FirebaseAnalytics
class IncomingController: UIViewController {
var camera: TVICameraCapturer?
#IBOutlet weak var img_user: UIImageView!
#IBOutlet weak var lblName: UILabel!
#IBOutlet weak var lbltopic: UILabel!
#IBOutlet weak var lblTimer: UILabel!
#IBOutlet weak var btn_speaker : UIButton!
#IBOutlet weak var btn_video: UIButton!
#IBOutlet weak var btn_mute: UIButton!
#IBOutlet weak var btn_extendcall: UIButton!
#IBOutlet weak var btn_endcall: UIButton!
//Video
#IBOutlet weak var btn_toogleMic : UIButton!
#IBOutlet weak var btnConnectAudio : UIButton!
#IBOutlet weak var btnFlipCamera : UIButton!
#IBOutlet weak var view_video: UIView!
#IBOutlet weak var lblviedoName: UILabel!
#IBOutlet weak var lblvideoTimer: UILabel!
#IBOutlet weak var lblvideoTopic: UILabel!
#IBOutlet weak var provider_previewView: TVIVideoView!
#IBOutlet weak var provider_remoteView: TVIVideoView!
#IBOutlet weak var provider_previewViewShadow: UIView!
#IBOutlet weak var btnView : UIView!
#IBOutlet weak var constrain_btnView: NSLayoutConstraint!
//UpdatedVideo
#IBOutlet weak var viewDisableVideo: UIView!
#IBOutlet weak var lblDisableViedoName: UILabel!
#IBOutlet weak var lblDisableVideoTimer: UILabel!
#IBOutlet weak var lblDisableTopic: UILabel!
#IBOutlet weak var lblDisableText: UILabel!
#IBOutlet weak var imgUserDisable: UIImageView!
var NotificationDict = NSMutableDictionary()
var Calltimer: Timer? = Timer()
var sec = 60
var min = 6
var audioDevice: TVIDefaultAudioDevice = TVIDefaultAudioDevice(block: {
do {
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayAndRecord, mode: AVAudioSessionModeVoiceChat, options: .mixWithOthers)
try AVAudioSession.sharedInstance().setPreferredSampleRate(48000)
try AVAudioSession.sharedInstance().setPreferredIOBufferDuration(0.01)
} catch {
print(error)
}
})
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
TwilioVideo.audioDevice = self.audioDevice
self.prepareLocalMedia()
connectToCalling(duration:NotificationDict["duration"] as! Int, consumerId: "\(NotificationDict["consumerId"] as! Int)", categoryName: NotificationDict["categoryTopic"] as! String, categoryImage: "", callId: NotificationDict["callId"] as! String)
}
//MARK:- Speaker Method
func setAudioOutputSpeaker(_ enabled: Bool) {
let session = AVAudioSession.sharedInstance()
try? session.setCategory(AVAudioSessionCategoryPlayAndRecord)
try? session.setMode(AVAudioSessionModeVoiceChat)
if enabled {
try? session.overrideOutputAudioPort(AVAudioSessionPortOverride.speaker)
} else {
try? session.overrideOutputAudioPort(AVAudioSessionPortOverride.none)
}
try? session.setActive(true)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
//MARK:- Custom Method
func connectToCalling(duration:Int, consumerId:String, categoryName:String, categoryImage:String, callId:String)
{
let Url = "\(Constant.tokenUrl)"
let Param = ["duration":duration, "consumerId":consumerId, "categoryName":categoryName, "categoryImage":categoryImage, "callId":callId] as [String:Any]
ApiResponse.onResponseKeyPost(url: Url, parms: Param as NSDictionary, completion: { (dict, errr) in
print("dict responce",dict)
if(errr == ""){
OperationQueue.main.addOperation {
accessToken = dict["providerToken"] as! String
let connectOptions = TVIConnectOptions.init(token: accessToken) { (builder) in
if let videoTrack = ProvideCallObject.localVideoTrack {
builder.videoTracks = [videoTrack]
}
// We will share a local audio track only if ExampleAVAudioEngineDevice is selected.
if let audioTrack = ProvideCallObject.localAudioTrack {
builder.audioTracks = [audioTrack]
}
userDef.set("\(dict["roomId"]!)", forKey: "roomId")
builder.roomName = "\(dict["roomId"]!)"
}
ProvideCallObject.Provideroom = TwilioVideo.connect(with: connectOptions, delegate: self)
}
}
})
}
#objc func updateCountDown() {
if btn_endcall.isUserInteractionEnabled == false
{
btn_endcall.isUserInteractionEnabled = true
}
if sec == 0 {
if min == 0
{
sec = 0
min = 0
goToFeedback()
Calltimer?.invalidate()
}else
{
sec = 59
min = min - 1
if min == 0 {
SystemSoundID.playFileNamed(fileName: "60 Seconds v2", withExtenstion: "m4a")
}
}
}else
{
var timeString = ""
sec = sec - 1
if min < 10
{
timeString = timeString + "0" + String(min)
if min == 2 && sec == 0{
SystemSoundID.playFileNamed(fileName: "2 Mins v2", withExtenstion: "m4a")
}
}
else
{
timeString = timeString + String(min)
}
if sec < 10
{
timeString = timeString + ":0" + String(sec)
}
else
{
timeString = timeString + ":" + String(sec)
}
lblTimer.text = "\(timeString) Free Minutes"
lblvideoTimer.text = "\(timeString) Free Minutes"
lblDisableVideoTimer.text = "\(timeString) remaining"
}
}
func prepareLocalMedia() {
if (ProvideCallObject.localAudioTrack == nil) {
ProvideCallObject.localAudioTrack = TVILocalAudioTrack.init(options: nil, enabled: true, name: "Microphone")
if (ProvideCallObject.localAudioTrack == nil) {
print("Failed to create audio track")
}
}
if (ProvideCallObject.localVideoTrack == nil) {
self.startPreview()
}
changeButtonImage(isVideoEnable: true)
}
//Video
func setupRemoteVideoView() {
self.provider_previewViewShadow.frame = CGRect(x: self.view_video.bounds.width - 132, y: self.view_video.bounds.height - 239, width: 112, height: 149)
self.provider_previewView.frame = self.provider_previewViewShadow.bounds
self.provider_remoteView.bringSubview(toFront: self.provider_previewViewShadow)
self.provider_remoteView.isHidden = false
}
// MARK: Private
func startPreview() {
if PlatformUtils.isSimulator {
return
}
camera = TVICameraCapturer(source: .frontCamera, delegate: self)
ProvideCallObject.localVideoTrack = TVILocalVideoTrack.init(capturer: camera!, enabled: true, constraints: nil, name: "Camera")
if (ProvideCallObject.localVideoTrack == nil) {
print("Failed to create video track")
} else {
ProvideCallObject.localVideoTrack!.addRenderer(self.provider_previewView)
let tap = UITapGestureRecognizer(target: self, action: #selector(IncomingController.flipCamera))
self.provider_previewView.addGestureRecognizer(tap)
}
}
#objc func flipCamera() {
if (self.camera?.source == .frontCamera) {
self.camera?.selectSource(.backCameraWide)
} else {
self.camera?.selectSource(.frontCamera)
}
}
func cleanupRemoteParticipant() {
if ((ProvideCallObject.remoteParticipant) != nil) {
if ((ProvideCallObject.remoteParticipant?.videoTracks.count)! > 0) {
let remoteVideoTrack = ProvideCallObject.remoteParticipant?.remoteVideoTracks[0].remoteTrack
remoteVideoTrack?.removeRenderer(self.provider_remoteView!)
self.provider_remoteView?.isHidden = true
}
}
ProvideCallObject.remoteParticipant = nil
}
}
// MARK:- TVIRoomDelegate
extension IncomingController : TVIRoomDelegate {
func callDetails(room_sid:String,callID:String) {
let params = ["room_sid":room_sid,"callId": callID,"isCallEnd":0] as [String : Any]
ApiResponse.onResponsePost(url: Constant.callDetailsTwilio, parms: params as NSDictionary) { (response, error) in
}
}
func didConnect(to room: TVIRoom) {
ProvideCallObject.localParticipant = ProvideCallObject.Provideroom?.localParticipant
ProvideCallObject.localParticipant?.delegate = self
Calltimer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(updateCountDown), userInfo: nil, repeats: true)
NotificationDict["roomsid"] = room.sid
btn_video.isUserInteractionEnabled = true
-----------------------------------------
if (room.remoteParticipants.count > 0) {
ProvideCallObject.remoteParticipant = room.remoteParticipants[0]
ProvideCallObject.remoteParticipant?.delegate = self
self.callDetails(room_sid: room.sid, callID: NotificationDict["callId"] as! String)
}
if !isHeadPhoneAvailabel(){
self.setAudioOutputSpeaker(true)
}
}
func room(_ room: TVIRoom, didDisconnectWithError error: Error?) {
self.cleanupRemoteParticipant()
}
func room(_ room: TVIRoom, didFailToConnectWithError error: Error) {
}
func room(_ room: TVIRoom, participantDidConnect participant: TVIRemoteParticipant) {
if (ProvideCallObject.remoteParticipant == nil) {
ProvideCallObject.remoteParticipant = participant
ProvideCallObject.remoteParticipant?.delegate = self
}
// print("Participant \(participant.identity) connected with \(participant.remoteAudioTracks.count) audio and \(participant.remoteVideoTracks.count) video tracks")
}
func room(_ room: TVIRoom, participantDidDisconnect participant: TVIRemoteParticipant) {
if (ProvideCallObject.remoteParticipant == participant) {
cleanupRemoteParticipant()
}
goToFeedback()
}
}
// MARK: TVIRemoteParticipantDelegate
extension IncomingController : TVILocalParticipantDelegate {
func localParticipant(_ participant: TVILocalParticipant, publishedVideoTrack: TVILocalVideoTrackPublication) {
ProvideCallObject.localVideoTrack = publishedVideoTrack.videoTrack as? TVILocalVideoTrack
}
}
// MARK: TVIRemoteParticipantDelegate
extension IncomingController : TVIRemoteParticipantDelegate {
func subscribed(to videoTrack: TVIRemoteVideoTrack,
publication: TVIRemoteVideoTrackPublication,
for participant: TVIRemoteParticipant) {
if (ProvideCallObject.remoteParticipant == participant) {
setupRemoteVideoView()
videoTrack.addRenderer(self.provider_remoteView!)
}
}
func remoteParticipant(_ participant: TVIRemoteParticipant,
enabledVideoTrack publication: TVIRemoteVideoTrackPublication) {
// print("Participant \(participant.identity) enabled \(publication.trackName) video track")
self.viewDisableVideo.isHidden = true
changeButtonImage(isVideoEnable: true)
}
func remoteParticipant(_ participant: TVIRemoteParticipant,
disabledVideoTrack publication: TVIRemoteVideoTrackPublication) {
self.viewDisableVideo.isHidden = false
changeButtonImage(isVideoEnable: false)
// print("Participant \(participant.identity) disabled \(publication.trackName) video track")
}
}
extension IncomingController : TVICameraCapturerDelegate {
func cameraCapturer(_ capturer: TVICameraCapturer, didStartWith source: TVICameraCaptureSource) {
// Layout the camera preview with dimensions appropriate for our orientation.
self.provider_previewView.shouldMirror = (source == .frontCamera)
}
}
// MARK: TVIVideoViewDelegate
extension IncomingController : TVIVideoViewDelegate {
func videoView(_ view: TVIVideoView, videoDimensionsDidChange dimensions: CMVideoDimensions) {
self.view.setNeedsLayout()
}
}
so it is possible to switch or change root controller in define case?
Thanks
This is the code I have. Basically when the user first chooses the quality of the dinning whether it is fine (20%), casual (15%), or a bar (11%). From there the user will then be asked on a 1-5 scale about the food quality, service, etc. Depending what number they choose, for example if the fine dinning was a 5/5 then it will ad 4% to the already 20 percent (each number will have a selected value to add to the tip). Can anyone figure out what's going on? Or if there is a better way about doing it.
import UIKit
#IBDesignable
class ViewController: UIViewController {
#IBOutlet weak var billTotal: UITextField!
var diningValue: Double = 0.0
#IBAction func toggleFineDining(sender: UIButton) {
sender.highlighted = sender.selected
if sender.selected == true{
diningValue = 0.20
}
}
#IBAction func toggleCasualDining(sender: UIButton) {
sender.highlighted = sender.selected
if sender.selected == true{
diningValue = 0.15
}
}
#IBAction func toggleBartender(sender: UIButton) {
sender.highlighted = sender.selected
if sender.selected == true{
diningValue = 0.11
}
}
#IBOutlet weak var service: UITextField!
#IBOutlet weak var friendliness: UITextField!
#IBOutlet weak var foodQuality: UITextField!
#IBOutlet weak var atmosphere: UITextField!
#IBOutlet weak var overall: UITextField!
#IBOutlet weak var recommend: UISwitch!
#IBOutlet weak var total: UILabel!
func totalBill(inputArray: [Double], value: Double, recommend: Bool)->Double{
var array: [Double] = []
let total = inputArray[0]
for var i = 1; i < inputArray.count; ++i {
array.append(inputArray[i] * 0.01)
}
let sum = array.reduce(0, combine: +)
var totalTip = Double()
if recommend == true{
totalTip = sum + value
} else {
if sum < 0.15 {
totalTip = 0.15
} else {
totalTip = sum + value
}
}
let tipValue = totalTip * total
return total + tipValue
}
#IBAction func calcTotal(sender: AnyObject) {
var valueList = [String(service.text), String(friendliness.text), String(foodQuality.text), String(atmosphere.text), String(overall.text)]
let list = doubleArray(valueList)
print(list)
let initialTotal = Double(billTotal.text!)
let total_bill = totalBill(list, value: diningValue, recommend: recommend.selected)
print("Total: ", total_bill)
print(String(format: "%.3f", Double(total_bill)))
total.text = "$" + String(format: "%.2f", Double(total_bill))
//diningValue = diningValue
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
func doubleArray(list: [String])->[Double]{
var doubledList: [Double] = []
for element in 0...list.count{
let doubledElement = Double(list[element])
doubledList.append(doubledElement!)
}
return doubledList
}
Your issue is in the last function:
func doubleArray(list: [String])->[Double]{
var doubledList: [Double] = []
for element in 0...list.count{
let doubledElement = Double(list[element])
doubledList.append(doubledElement!)
}
return doubledList
}
Your loop is looping from 0 to list.count and it crashes at list[element] when element is list.count (Out of Index)
You need to change from 0...list.count to 0..<list.count:
func doubleArray(list: [String])->[Double]{
var doubledList: [Double] = []
for element in 0..<list.count{
let doubledElement = Double(list[element])
doubledList.append(doubledElement!)
}
return doubledList
}
I would recommend you to do it in swifty way:
func doubleArray(list: [String])->[Double]{
var doubledList: [Double] = []
for element in list {
doubledList.append(Double(element) ?? 0.0)
}
return doubledList
}
A shorter version using map function:
func doubleArray(list: [String])->[Double]{
return list.map( { Double($0) ?? 0.0 })
}