swift how to update first controller from logic in second one vai protocol and delegate pattern? - swift

I have a label in first view controller ViewController, and a func getting date avery second in second vc. I'd like to update label in first after timer starts in second. is it good to use protocol-delegate pattern? at this moment it is not working, time is going but not updating the view in first VC
my struct for protocol
protocol ViewControllerDelegate: class {
func changeLabelText(textToPass: String)
}
in first viewController
class ViewController: UIViewController, ViewControllerDelegate {
#IBOutlet weak var mainLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
}
func changeLabelText(textToPass: String) {
self.mainLabel.text = textToPass
self.view.layoutIfNeeded()
}
#IBAction func buttonTapped(_ sender: UIButton) {
let nextVC = storyboard?.instantiateViewController(withIdentifier: "SecondViewController") as! SecondViewController
nextVC.delegateSubscriber = self
present(nextVC, animated: true, completion: nil)
}
}
in secondVC
class SecondViewController: UIViewController {
//MARK: speed timer feature 1/3
private weak var timer: Timer?
private var timerDispatchSourceTimer : DispatchSourceTimer?
weak var delegateSubscriber : ViewControllerDelegate?
#IBOutlet weak var myTxtField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
startTimer(every: 1)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
print("appeared")
stopTimer()
}
private func startTimer(every timeInterval: TimeInterval) {
if #available(iOS 10.0, *) {
timer = Timer.scheduledTimer(withTimeInterval: timeInterval, repeats: true) { [weak self] _ in
let dateToPass = Date().description
print(dateToPass)
self?.delegateSubscriber?.changeLabelText(textToPass: dateToPass)
}
}
}
//MARK: speed timer feature 3/3
private func stopTimer() {
timer?.invalidate()
//timerDispatchSourceTimer?.suspend() // if you want to suspend timer
timerDispatchSourceTimer?.cancel()
}
#IBAction func buttonTapped(_ sender: UIButton) {
// delegateSubscriber?.changeLabelText(textToPass: self.myTxtField.text ?? "error")
dismiss(animated: true, completion: nil)
}
}

Just remove [weak self] from Timer closure
timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in
let dateToPass = Date().description
print(dateToPass)
self.delegateSubscriber?.changeLabelText(textToPass: dateToPass)
}
... then self isn't optional

Related

How to hide NavigationBar from sub view?

I am facing an issue of NavigationBar, I dont want it in subview (child view), I also used setNavigationBarHidden() method to hide but it is not working.
class VehicleSavingPopupViewController: UIViewController {
#IBOutlet weak var bottomView: UIView!
#IBOutlet weak var backGroundView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
animateView()
backGroundView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(backGroundViewTapped(_:))))
// Do any additional setup after loading the view.
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
navigationController?.setNavigationBarHidden(true, animated: false)
}
private func animateView(){
UIView.animate(withDuration: 0.5, delay: 0, options: [.transitionCurlDown],
animations: { [weak self] in
guard let self = `self` else {return}
self.bottomView.center.y -= self.bottomView.bounds.height
}, completion: nil)
}
I set this in my own navigationController class but it should work in your viewDidLoad() method as well.
navigationController?.navigationBar.isHidden = true

How can I make variables inside an IBAction public to all view controllers?

So I'm trying to make a Chess Time app, where both players have access to a clock and they change the time between bullet(3minutes), Blitz(5minutes), and Rapid(10Minutes). Well in my second view controller SettingsController I made 3 IBActions UIButtons for this.
#IBAction func bulletPressed(_ sender: UIButton) {
var storedTime = bullet
self.delegate?.storedTimeTimer()
self.navigationController?.popViewController(animated: true)
}
#IBAction func blitzPressed(_ sender: UIButton) {
var storedTime = blitz
}
#IBAction func rapidPressed(_ sender: UIButton) {
var storedTime = rapid
}
This is my SettingsController, my whole point is trying to get the storedTime into the first controller. I tried to use a delegate, but I couldn't get it to work.
Here is the full First Controller:
import UIKit
class ChessTimer: UIViewController {
#IBOutlet weak var playerTimer1: UILabel!
#IBOutlet weak var playerTimer2: UILabel!
var timer = Timer()
var time = 10
var isTimerRunning = false
override func viewDidLoad() {
super.viewDidLoad()
if isTimerRunning == false {
runTimer()
}
}
#IBAction func restartButton(_ sender: UIButton) {
}
#IBAction func pausePressed(_ sender: UIButton) {
timer.invalidate()
}
#IBAction func settingsPressed(_ sender: UIButton) {
performSegue(withIdentifier: "goToSettings", sender: self)
}
func runTimer() {
timer = Timer.scheduledTimer(timeInterval: 1, target: self,selector:
(#selector(ChessTimer.updateTimer)),userInfo: nil, repeats: true)
isTimerRunning = true
}
#objc func updateTimer() {
if storedTime! < 1 {
timer.invalidate()
playerTimer1.text = "00:00"
playerTimer2.text = "00:00"
}
else {
storedTime! -= 1
playerTimer1.text = prodTimeString(time: TimeInterval(storedTime)!)
}
}
func prodTimeString(time: TimeInterval) -> String {
let prodMinutes = Int(time) / 60 % 60
let prodSeconds = Int(time) % 60
return String(format: "%02d:%02d", prodMinutes, prodSeconds)
}
#IBAction func playerButton1(_ sender: UIButton) {
}
#IBAction func playerButton2(_ sender: UIButton) {
}
}
extension ChessTimer: SettingsControllerDelegate {
func storedTimeTimer() {
}
}
This is the second full controller
import UIKit
class SettingsController: UIViewController {
var bullet = "03:00"
var blitz = "05:00"
var rapid = "10:00"
var storedTime = 0
var delegate: SettingsControllerDelegate?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
#IBAction func bulletPressed(_ sender: UIButton) {
var storedTime = bullet
self.delegate?.storedTimeTimer()
self.navigationController?.popViewController(animated: true)
}
#IBAction func blitzPressed(_ sender: UIButton) {
var storedTime = blitz
}
#IBAction func rapidPressed(_ sender: UIButton) {
var storedTime = rapid
}
}
protocol SettingsControllerDelegate {
func storedTimeTimer()
}
This can be achieved using a Constants.swift file.
Click File -> New -> File -> Swift File and then name it Constants.swift.
In Constants.swift, declare var storedTime = 0.
Delete var storedTime = 0 from your view controllers, you'll only need it in Constants.swift. (So delete it from SettingsController, etc.)
The storedTime variable will now be public to all view controllers. 👍
Hope this helps someone!

How can I transfer UISlider value on SecondViewController to Alarm on FirstViewController?

I have two ViewControllers, FirstViewController and SecondViewController.
And there is UISlider on SecondViewController. There is an alarm on FirstViewController.
Now I want to transfer UISlider value, which is volume to alarm on FirstViewController.
ps. The function what I want to make is totally same as default iPhone setting slider.
I would be so happy so if you guys give me your knowledge, please.
import UIKit
import AVFoundation
class FirstViewController: UIViewController, AVAudioPlayerDelegate,
UITableViewDelegate, UITableViewDataSource{
let TODO = ["A", "B", "C"]
let notificationCenter = NotificationCenter.default
var volume = Float()
var counter = 0
var timer = Timer()
var startTime:Double = 0.0
var audioPlayer: AVAudioPlayer!
#IBOutlet weak var tableView: UITableView!
#IBAction func firstSwitch(_ sender: UISwitch)
{
if (sender).isOn
{
timer = Timer.scheduledTimer(withTimeInterval: 1 * 1, repeats: false, block: { timer in
self.audioPlayer.play()
self.audioPlayer.numberOfLoops = -1
print(self.audioPlayer.isPlaying)
})
}else{
timer.invalidate()
print("switch1stopped")
self.audioPlayer.stop()
}
}
notificationCenter.addObserver(self, selector: #selector(catchNotification(notification:)), name: NSNotification.Name(rawValue: "test"), object: nil)
}
#objc func catchNotification(notification: Notification) -> Void {
print("Catch notification")
audioPlayer.volume = volumeChane.value
//Use of unresolved identifier 'volumeChane'
}
///////////////////////////////////////////////////////////////////////////
import UIKit
import AVFoundation
class SecondViewController: UIViewController {
var audioPlayer: AVAudioPlayer!
let notificationCenter = NotificationCenter.default
#IBOutlet weak var volumeSlider: UISlider!
#IBOutlet weak var volumeLabel: UILabel!
#IBAction func volumeChange(_ sender: UISlider)
{
volumeLabel.text = String(Int(sender.value))
volumeSlider.value = sender.value
audioPlayer.volume = volumeSlider.value
notificationCenter.post(name: NSNotification.Name(rawValue: "test"), object: nil)
audioPlayer.play()
}
override func viewDidLoad()
{
super.viewDidLoad()
if let url=Bundle.main.url(forResource:"Alarm",withExtension:".mp3" )
{
do {
audioPlayer = try AVAudioPlayer(contentsOf:url)
audioPlayer?.play(atTime:1 * 10)
}catch{
audioPlayer = nil
}
}else{
fatalError("Url is nil")
}
}
extension Notification.Name
{
static let myNotificationName = Notification.Name("test")
}
Using NSNotificationCenter for a simple task such as this might be an overkill. You usually use closures to catch data changes in your components or view controllers.
In the SecondViewController create a variable containing a closure:
var onVolumeChange: ((value: Float) -> Void)?
Call it in the IBAction that monitors slider's onChange event.
#IBAction func volumeChange(_ sender: UISlider)
{
self.onVolumeChange?(sender.value)
}
Pass the onVolumeChange closure from the FirstViewController when navigating to the second one. I'm not sure how you perform navigation so I'll assume you do it programmatically.
let vc = UIStoryboard(name: "main", bundle: nil).instantiateViewController(withIdentifier: "SecondViewController")
vc.onVolumeChange = { value in
audioPlayer.volume = value
}
self.navigationController?.pushViewController(vc, animated: true)

Swift delegate beetween two VC without segue

I have 3 classes:
ChatLogControoller
GetImageFromLibraty(NSObject class)
ImagePreviewViewController
I want to press a clip from the first VC, then open the media library to pick an image. Then the selected image is passed to the third VC as a previewController. Then if I select 'done' I want to pass it to the first VC.
1st VC
class ChatLogControoller: UICollectionViewController, UICollectionViewDelegateFlowLayout, NSFetchedResultsControllerDelegate, UINavigationControllerDelegate, UIImagePickerControllerDelegate, DataSentDelegate {
func recievePhoto(data: UIImage) {
imageFromView = data
print("-------\(imageFromView = data)")
}
override func viewDidLoad() {
super.viewDidLoad()
let vc = ImagePreviewController()
self.vc.delegate = self
}
2nd class its just picker of image, so i pass image to 3rd VC and this image appears on imageView of 3rd VC successfully!
my 3rd VC
protocol DataSentDelegate {
func recievePhoto(data: UIImage)
}
class PreviewController: UIViewController, UIScrollViewDelegate {
var delegate : DataSentDelegate? = nil
var aImageView: UIImageView!
var aImage: UIImage!
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Add", style: .plain, target: self, action: #selector(actionSend))
navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Cancel", style: .plain, target: self, action: #selector(actionBack))
}
#objc func actionBack() {
dismiss(animated: false, completion: nil)
}
#objc func actionSend() {
let data = aImageView.image
delegate?.recievePhoto(data: data!)
dismiss(animated: true, completion: nil)
}
You need to create one more protocol in your SecondViewController to Pass that delegate from ThirdViewController to FirstViewController.
FirstViewController:
import UIKit
class ViewController: UIViewController, DataSentDelegate, dataSentDelegate {
#IBOutlet weak var imagefromThirdVC: UIImageView!
var thirdVCImage: UIImage!
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func buttonTapped(_ sender: Any) {
let vc = storyboard?.instantiateViewController(withIdentifier: "ViewController2") as! ViewController2
vc.delegate = self
self.navigationController?.pushViewController(vc, animated: true)
}
func goToThirdVC() {
let vc = storyboard?.instantiateViewController(withIdentifier: "ViewController3") as! ViewController3
vc.delegate = self
self.navigationController?.pushViewController(vc, animated: true)
}
func recievePhoto(data: UIImage) {
thirdVCImage = data
imagefromThirdVC.image = thirdVCImage
}
}
SecondViewController:
import UIKit
protocol dataSentDelegate {
func goToThirdVC()
}
class ViewController2: UIViewController {
#IBOutlet weak var passingImage: UIImageView!
var delegate: dataSentDelegate? = nil
var images: UIImage!
override func viewDidLoad() {
super.viewDidLoad()
images = UIImage(named: "screen")
}
#IBAction func actionButton(_ sender: Any) {
self.delegate?.goToThirdVC()
}
}
ThirdViewController:
import UIKit
protocol DataSentDelegate {
func recievePhoto(data: UIImage)
}
class ViewController3: UIViewController {
var delegate: DataSentDelegate? = nil
#IBOutlet weak var passedImageView: UIImageView!
var passedImage: UIImage!
override func viewDidLoad() {
super.viewDidLoad()
passedImage = UIImage(named: "screen")
passedImageView.image = passedImage
}
#IBAction func action(_ sender: Any) {
let data = passedImageView.image
delegate?.recievePhoto(data: data!)
// delegate?.goToFirstVC()
guard let viewControllers = self.navigationController?.viewControllers else {
return
}
for firstViewController in viewControllers {
if firstViewController is ViewController {
self.navigationController?.popToViewController(firstViewController, animated: true)
break
}
}
}
}

how to get a textview to autoscroll down when theres a new line

in swift 4.0 for cocoa applications
how do you get a textview to autoscroll down when theres a new line
of text added into it?
is there a built in function for this?
i can't seem to find a way to do this.
chatplace.scroll(<#T##point: NSPoint##NSPoint#>)
Here's a simple example that works:
import UIKit
class ViewController: UIViewController {
var count = 0
var timer:Timer?
#IBOutlet weak var textView: UITextView!
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
textView.isScrollEnabled = true
textView.becomeFirstResponder()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
timer = Timer(timeInterval: 0.125, repeats: true, block: { (timer) in
self.count += 1
self.textView.text.append("\n\(self.count) This is another line")
})
if let timer = timer {
RunLoop.current.add(timer, forMode: .commonModes)
}
}
}
The result:
If you want to scroll programmatically this should do it:
func scrollToEnd(_ someTextView:UITextView) {
let bottom = NSMakeRange(someTextView.text.lengthOfBytes(using: .utf8)-1, 1)
someTextView.scrollRangeToVisible(bottom)
}