(UILabel) nil, Cannot insert text in the label - ios

Good day,
I seem to have such a simple problem but I just can not wrap my head around it.
I have a container view inside a view controller. In that container I have few labels. The container has its own view controller. In the view controller for the container I have a timer running and I want that label to show the timer. But every time I use that label the app crashes with
"Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value"
If I comment the line out that has this label then everything runs fine.
#IBOutlet weak var timeLabel: UILabel!
var counter = 0.0
var timer = Timer()
var isRunning = false
func startStopTimer () {
if isRunning {
timer.invalidate()
isRunning = false
}else {
timer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(updateTimer), userInfo: nil, repeats: true)
isRunning = true
}
}
#objc func updateTimer() {
counter = counter + 0.1
timeLabel.text = String(counter)
}
This is the first time I play around with container view in the Main storyboard.
Anyone that knows what I am doing wrong or has suggestion what I can try to change?
Thanks
Jonas
Full Code
class MainViewController: UIViewController {
#IBOutlet weak var topContainer: UIView!
#IBOutlet weak var informationContainer: UIView!
#IBOutlet weak var startStopButtonOutlet: UIButton!
let informationContainerVC = InformationContainerViewController()
override func viewDidLoad() {
super.viewDidLoad()
setupView()
}
func setupView() {
topContainer.layer.cornerRadius = 5
topContainer.layer.masksToBounds = true
informationContainer.layer.cornerRadius = 5
informationContainer.layer.masksToBounds = true
startStopButtonOutlet.layer.cornerRadius = 5
startStopButtonOutlet.layer.masksToBounds = true
}
#IBAction func startStopButton_TouchUpInside(_ sender: UIButton) {
informationContainerVC.startStopTimer()
UIButton.animate(withDuration: 0.1, delay: 0.0, options: [.allowUserInteraction, .curveEaseOut], animations: {
sender.transform = CGAffineTransform.identity
}, completion: nil)
if informationContainerVC.isRunning {
startStopButtonOutlet.setTitle("Push to Pause", for: .normal)
}else {
startStopButtonOutlet.setTitle("Push to Start", for: .normal)
}
}
#IBAction func startStopButton_TouchDown(_ sender: UIButton) {
UIButton.animate(withDuration: 0.1, delay: 0.0, options: [.allowUserInteraction, .curveEaseIn], animations: {
sender.transform = CGAffineTransform(scaleX: 0.9, y: 0.9)
if self.informationContainerVC.isRunning {
sender.backgroundColor = UIColor.white.withAlphaComponent(0.5)
}else {
sender.backgroundColor = UIColor.green.withAlphaComponent(0.8)
}
}, completion: nil)
}
#IBAction func startStopButton_TouchUpOutside(_ sender: UIButton) {
UIButton.animate(withDuration: 0.1, delay: 0.0, options: [.allowUserInteraction, .curveEaseOut], animations: {
sender.transform = CGAffineTransform.identity
}, completion: nil)
}
}
Here is the code for the container view controller
class InformationContainerViewController: UIViewController {
#IBOutlet weak var timeLabel: UILabel!
var counter = 0.0
var timer = Timer()
var isRunning = false
func startStopTimer () {
if isRunning {
timer.invalidate()
isRunning = false
}else {
timer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(updateTimer), userInfo: nil, repeats: true)
isRunning = true
}
}
#objc func updateTimer() {
counter = counter + 0.1
timeLabel.text = String(counter)
}
}

When you say let informationContainerVC = InformationContainerViewController() you are creating a new instance of InformationContainerViewController that is not linked to the storyboard, so none of the outlets are set.
You need to get a reference to the view controller instance that is actually in your container view. You can do this in prepare(for segue:); If you look at your storyboard you will see that there is an embed segue that links your containing view controller to the contained view controller.
In your MainViewController:
var informationContainerVC: InformationContainerViewController?
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destVC = segue.destination as? InformationContainerViewController {
self.informationContainerVC = destVC
}
}
#IBAction func startStopButton_TouchUpInside(_ sender: UIButton) {
informationContainerVC?.startStopTimer()
UIButton.animate(withDuration: 0.1, delay: 0.0, options: [.allowUserInteraction, .curveEaseOut], animations: {
sender.transform = CGAffineTransform.identity
}, completion: nil)
if informationContainerVC?.isRunning {
startStopButtonOutlet.setTitle("Push to Pause", for: .normal)
} else {
startStopButtonOutlet.setTitle("Push to Start", for: .normal)
}
}
Now you will have a reference to the correct view controller instance

Related

How to change images cross dissolve xcode 9?

I want to changes background images in a nice way like cross dissolve...
I manage to let the image change within 3 seconds.
Can you help me with a code to let it change in a nice way?
var timer = Timer()
var counter = 0
#IBOutlet weak var imageBG: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
repated()
}
func repated() {
timer = Timer.scheduledTimer(timeInterval: 3, target: self, selector: #selector(ViewController.updatecounter), userInfo: nil, repeats: true)
}
#objc func updatecounter() {
counter += 1
if counter == 14 {
counter = 1
}
imageBG.image = UIImage(named: "\(counter).png")
}
Into your upodatecounter you can use an UIView transition like this:
#objc func updatecounter() {
counter += 1
if counter == 14 {
counter = 1
}
UIView.transition(with: imageBG,
duration:3.0,
options: .transitionCrossDissolve,
animations: { self.imageBG?.image = UIImage(named: "\(counter).png") },
completion: nil)
}

How to repeat the animation when scrolling the TextView with Swift?

Swift 4: I'm trying to scroll the textView when tap an Button. Here are my codes:
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var txtViewStory: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
}
var scrollingTimer = Timer()
#IBAction func scroll(_ sender: UIButton) {
scrollingTimer = Timer.scheduledTimer(timeInterval: 0.001, target: self, selector: #selector(ViewController.startScrolling), userInfo: nil, repeats: true)
scrollingTimer.fire()
}
#objc func startScrolling(){
var currentOffset = txtViewStory.contentOffset
currentOffset = CGPoint(x: currentOffset.x, y: currentOffset.y + 1)
txtViewStory.setContentOffset(currentOffset, animated: false)
}
}
Here is how it looks like:
The animation will stop when the TextView scrolls to the bottom. But I would like it to repeat scrolling again and again. Is there any way to get it work? Any suggestions would be appreciated.
Very dirty code but should work ;>
#IBAction func scroll(_ sender: UIButton) {
scrollingTimer.invaldiate()
scrollingTimer = nil
txtViewStory.setContentOffset(CGPoint(x: currentOffset.x, y: 0), animated: false)
scrollingTimer = Timer.scheduledTimer(timeInterval: 0.001, target: self, selector: #selector(ViewController.startScrolling), userInfo: nil, repeats: true)
scrollingTimer.fire()
}

I want to allow a live stream to be started by a 1.5 second long tap, but my code requires users to hold down the button the entire stream

I need to allow users to begin live streaming video to their friends by pressing an additionalCameraButton for ~1.5 seconds. However currently they have to hold it down the entire time, and the moment they remove their finger from the additionalCameraButton, the cameraView is cancelled.
I'm working with someone's old code that I just converted from Swift 2.2 to Swift 3 and I see what they did, but when I alter it slightly to get the minimumDuration for the longTap gesture, I get fatal errors.
How would any of you go about altering the code to allow for a 1.5 second longTap instead of having to hold it down indefinitely? On line 58 I have the code to add the minimumDuration for the longTap but adding it causes all sorts of errors as upon letting go of the button, the cameraView is still canceled even though the live stream has started.
import UIKit
import AVFoundation
protocol CameraViewDelegate {
func startStopRecordingVideo(_ isStart: Bool)
func startStopStream(_ isStart: Bool)
func cancelCameraView()
func changeCamera()
func chooseVideo()
}
class CameraView: UIView, UIGestureRecognizerDelegate {
#IBOutlet weak var flashBtn: UIButton!
#IBOutlet weak var screenView: UIView!
#IBOutlet weak var shootBtn: UIButton!
#IBOutlet weak var changeCameraBtn: UIButton!
#IBOutlet weak var cancelBtn: UIButton!
#IBOutlet weak var alphaView: UIView!
#IBOutlet weak var shootBtnContainerView: UIView!
var delegate : CameraViewDelegate?
var isRecording : Bool = false
var isStreaming: Bool = false
var circleLayer: CAShapeLayer?
var timer: Timer?
//MARK: SYSTEMS METHODS
class func instanceFromNib() -> CameraView {
return UINib(nibName: "View", bundle: nil).instantiate(withOwner: nil, options: nil)[0] as! CameraView
}
override func awakeFromNib() {
layoutIfNeeded()
shootBtnContainerView.layer.cornerRadius = shootBtnContainerView.frame.size.width/2
shootBtn.layer.cornerRadius = shootBtn.frame.size.width/2
let tap = UITapGestureRecognizer(target: self, action: #selector(doubleTapped))
tap.numberOfTapsRequired = 2
shootBtn.addGestureRecognizer(tap)
let hideTap = UITapGestureRecognizer(target: self, action: #selector(hideTapped))
hideTap.delegate = self
alphaView.addGestureRecognizer(hideTap)
let hold = UILongPressGestureRecognizer(target: self, action: #selector(longTap))
shootBtn.addGestureRecognizer(hold)
//hold.minimumPressDuration = 1.5
if Defaults.sharedDefaults.userKnowAboutCamera {
alphaView.isHidden = true
}
let alert = UIAlertController(title: "Testing!", message: nil, preferredStyle: .alert)
let action = UIAlertAction(title: "Thank You", style: .default, handler: nil)
alert.addAction(action)
}
//MARK: - CIRcLE ANIMATION
func createCirclePath() {
let circlePath = UIBezierPath(arcCenter: shootBtnContainerView.center, radius: shootBtnContainerView.frame.size.width/2, startAngle: 0.0, endAngle: CGFloat(.pi * 2.0), clockwise: true)
circleLayer = CAShapeLayer()
circleLayer!.path = circlePath.cgPath
circleLayer!.fillColor = UIColor.clear.cgColor
circleLayer!.strokeColor = UIColor.red.cgColor
circleLayer!.lineWidth = 3.0;
circleLayer!.strokeEnd = 0.0
layer.addSublayer(circleLayer!)
}
func animateCircle(_ duration: TimeInterval) {
circleLayer?.removeFromSuperlayer()
createCirclePath()
circleLayer!.strokeEnd = 0.0
let animation = CABasicAnimation(keyPath: "strokeEnd")
animation.duration = duration
animation.fromValue = 0
animation.toValue = 1
animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
circleLayer!.strokeEnd = 1.0
circleLayer!.add(animation, forKey: "animateCircle")
}
// SHOW HIDE ALPHA VIEW
func showHideAlphaView(_ isHide: Bool){
Defaults.sharedDefaults.userKnowAboutCamera = true
var alpha: Float = 0.0
if isHide { alpha = 0.0 } else { alpha = 0.6 }
UIView.animate(withDuration: 1.5, animations: {
self.alphaView.alpha = CGFloat(alpha)
}, completion: nil)
}
// ACTIONS
func hideTapped(){
showHideAlphaView(true)
}
func doubleTapped() {
delegate?.chooseVideo()
}
func longTap(_ sender: UILongPressGestureRecognizer) {
print("tapping")
if sender.state == .began {
SSContact.shared.isStreaming(public: true, to: nil, verification: { (error) in
if error != nil {
print(error!.localizedDescription)
}
}, live: { (live, views) in
print("Live: \(live) :: With \(views) Views!")
})
isRecording = !isRecording
delegate?.startStopRecordingVideo(isRecording)
isStreaming = !isStreaming
delegate?.startStopStream(isStreaming)
} else if sender.state == .ended {
SSContact.shared.stopStreaming()
isRecording = !isRecording
delegate?.startStopRecordingVideo(isRecording)
isStreaming = !isStreaming
delegate?.startStopStream(isStreaming)
delegate?.cancelCameraView()
}
}
func updateTimer() {
isRecording = !isRecording
delegate?.startStopRecordingVideo(isRecording)
timer?.invalidate()
}
#IBAction func shootVideo(_ sender: AnyObject) {
if !isRecording{
timer = Timer.scheduledTimer(timeInterval: 20.0, target: self, selector: #selector(CameraView.updateTimer), userInfo: nil, repeats: true)
animateCircle(20)
} else {
timer?.invalidate()
circleLayer?.removeAnimation(forKey: "animateCircle")
circleLayer!.strokeEnd = 0.0
}
isRecording = !isRecording
delegate?.startStopRecordingVideo(isRecording)
}
#IBAction func cancelPressed(_ sender: AnyObject) {
delegate?.cancelCameraView()
}
#IBAction func changeCameraPressed(_ sender: AnyObject) {
delegate?.changeCamera()
}
#IBAction func flashBtnPressed(_ sender: AnyObject) {
let device = AVCaptureDevice.defaultDevice(withMediaType: AVMediaTypeVideo)!
if (device.hasTorch) {
do {
try device.lockForConfiguration()
if device.torchMode == .on {
device.torchMode = .off
flashBtn.setImage(UIImage(named: "Flash"), for: UIControlState())
} else {
flashBtn.setImage(UIImage(named: "NoFlash"), for: UIControlState())
do {
try device.setTorchModeOnWithLevel(1.0)
} catch {
print(error.localizedDescription)
}
}
device.unlockForConfiguration()
} catch {
print(error.localizedDescription)
}
}
}
#IBAction func loadVideoPressed(_ sender: AnyObject) {
}
}
Look at your longTap method for the UILongPressGestureRecognizer, you say if sender.state == .ended to basically stop streaming, so whenever the user releases the touch it will stop:
func longTap(_ sender: UILongPressGestureRecognizer) {
if sender.state == .began {
// Start the stream
} else if sender.state == .ended {
SSContact.shared.stopStreaming()
isRecording = !isRecording
delegate?.startStopRecordingVideo(isRecording)
isStreaming = !isStreaming
delegate?.startStopStream(isStreaming)
delegate?.cancelCameraView()
}
}
I would suggest maybe having a method that manages starting and stopping the stream based on your isRecording boolean, and changing the way isRecording is declared to use a didSet:
var isRecording: Bool = false {
didSet{
// Manage the stream based on how the variable is set
self.manageStream(start: isRecording)
}
}
func manageStream(start: Bool) {
if start {
// Start the stream
SSContact.shared.isStreaming(public: true, to: nil, verification: { (error) in
if error != nil {
print(error!.localizedDescription)
}
}, live: { (live, views) in
print("Live: \(live) :: With \(views) Views!")
})
isRecording = !isRecording
delegate?.startStopRecordingVideo(isRecording)
isStreaming = !isStreaming
delegate?.startStopStream(isStreaming)
}
else {
// Stop the stream
SSContact.shared.stopStreaming()
isRecording = !isRecording
delegate?.startStopRecordingVideo(isRecording)
isStreaming = !isStreaming
delegate?.startStopStream(isStreaming)
delegate?.cancelCameraView()
}
}
Now you can use the longPress recognizer to start and stop the streaming :
func longTap(_ sender: UILongPressGestureRecognizer) {
isRecording = !isRecording
}
You could even change the duration of the press, like say you wanted the minimum press duration to be much less to turn the stream off as opposed to turning it on:
func longTap(_ sender: UILongPressGestureRecognizer) {
isRecording = !isRecording
if isRecording {
// Make the minimum duration zero so they just need to tap to turn it off
sender.minimumPressDuration = 0
} else {
// Make the minimum duration back to 1.5 seconds so they have to hold to turn it on
sender.minimumPressDuration = 1.5
}
}
Your UILongPressGestureRegonizers target is longTap(_:). This method will be executed whenever the gesture recognizer fires an event. Lets look your implementation of this method then:
func longTap(_ sender: UILongPressGestureRecognizer) {
print("tapping")
if sender.state == .began {
// ...
delegate?.startStopStream(isStreaming)
} else if sender.state == .ended {
SSContact.shared.stopStreaming()
// ...
}
}
I replaced some code with // ... to focus on what matters here. As you can see, a UILongPressGestureReconizer can have multiple states (also see the API Reference). In your code you are handling two of them: .began and .ended.
The gesture recognizers state will be .began once your gesture has been recognized (ie. the long press has been executed by the user). In this if branch you are starting your camera recording and also your stream. In your .ended branch on the other hand you are stopping your stream. The state will be .ended when the gesture ends (ie. when the user lifts his finger, as the long press has now ended).
If you want the stream to continue after the long press, simple remove the .ended branch.

Push the next view controller after a delay?

I'm trying to push the next view controller after a delay, using a timer.
I already made an app with an one second timer, but I'd like it to stop after a period of time.
import UIKit
class Level1: UIViewController {
var timerCount = 0
var timmerRunning = false
var timer = NSTimer()
#IBOutlet weak var timerLabel: UILabel!
func Counting() {
timerCount += 1
timerLabel.text = "\(timerCount)"
}
#IBAction func startButton(sender: UIButton) {
if !timmerRunning {
timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: Selector("Counting"), userInfo: nil, repeats: true)
timmerRunning = true
}
}
#IBAction func stopButton(sender: UIButton) {
if timmerRunning {
timer.invalidate()
timmerRunning = false
}
}
#IBAction func restartButton(sender: UIButton) {
timerCount = 0
timerLabel.text = "0"
}
}
You can check your timerCount variable in the counting function like that and after it has reached your desired period of time, stop the timer and present the new ViewController:
func Counting() {
timerCount += 1
timerLabel.text = "\(timerCount)"
if timerCount == desiredPeriodOfTime {
self.timer.invalidate()
self.timmerRunning = false
dispatch_async(dispatch_get_main_queue()){
let viewController = NewViewController()
self.presentViewController(viewControllerToPresent: viewController, animated: true, completion: nil)
}
}
}

How do I use a UIButton while it's animating

I have found one similar question to this but it did not answer my question.
I have a UIButton which is animating from the bottom of the screen to the top. I would like to be able to use the button while it is moving. Now, the button can only be used when the animation has finished and the button is no longer animating. Also, I've heard that I might need to use something called NSTimer?
class ViewController: UIViewController {
#IBAction func button2(sender: UIButton) {
button.hidden = false
button.center = CGPointMake(126, 380);
UIView.animateKeyframesWithDuration(3, delay: 0, options: .AllowUserInteraction,
animations: { () -> Void in
self.button.center = CGPointMake(126, 130 )
}) { (_) -> Void in
}
}
#IBOutlet var label: UILabel!
#IBOutlet var button: UIButton!
#IBAction func button1(sender: UIButton) {
button.hidden = true
label.hidden = false
}
override func viewDidLoad() {
super.viewDidLoad()
button.hidden = true
label.hidden = true
}
}
You have to use a CADisplayLink. For example:
#IBOutlet var button2: UIButton!
#IBAction func button3(sender: UIButton)
{
label.hidden = false
button2.hidden = true
}
#IBAction func button1(sender: UIButton)
{
button2.frame = CGRectMake(120, 400, 100, 100)
let displayLink = CADisplayLink(target: self, selector: "handleDisplayLink:")
displayLink.addToRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode)
}
func handleDisplayLink(displayLink: CADisplayLink)
{
var buttonFrame = button2.frame
buttonFrame.origin.y += -2
button2.frame = buttonFrame
if button2.frame.origin.y <= 50
{
displayLink.invalidate()
}
}
You can also check this question: Moving a button in swift using animate with duration with constraints and detecting a touch during it

Resources