How do I use a UIButton while it's animating - ios

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

Related

(UILabel) nil, Cannot insert text in the label

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

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.

Can't hide UIButton?

I'm trying to have a custom UIButton become hidden once it's pressed a certain number of times...but I'm at a loss.
Having exhausted my limited knowledge and consulting the Apple's documentation as well as the internet for the better part of 3 hours, I've finally made my way here. I've been learning Swift for a short while now and am making an effort to become more familiar with it. This is my first object-oriented language and it's testing me to say the least. Any help with this more likely than not ridiculously simple problem is very much appreciated.
import UIKit
class ViewController: UIViewController{
#IBOutlet weak var buttonMessageDisplay: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
buttonPressed()
}
var tapcount = 0
let buttonMessage : [String] = [/* long array of strings */]
func buttonPressed() {
let button = UIButton(type:.Custom) as UIButton
button.frame = CGRectMake(0, 0, 100, 100)
button.center = CGPointMake(self.view.frame.size.width/2, self.view.frame.size.height/2);
button.backgroundColor = UIColor.redColor()
button.layer.borderColor = UIColor.blackColor().CGColor
button.layer.borderWidth = 3
button.layer.cornerRadius = 0.5 * button.bounds.size.width
button.setTitle("", forState: UIControlState.Normal)
button.addTarget(self, action: "buttonPressed", forControlEvents: .TouchUpInside)
view.addSubview(button)
switch tapcount {
case 19...23:
//Hides the button
button.hidden = true
buttonMessageDisplay.text = buttonMessage[tapcount]
case 24...31:
//Unhides the button
button.hidden = false
buttonMessageDisplay.text = buttonMessage[tapcount]
default:
buttonMessageDisplay.text = buttonMessage[tapcount]
}
print("Tap Count: \(tapcount)")
++tapcount
}
Updated with Gesture Recognizer:
import UIKit
class ViewController: UIViewController{
#IBOutlet weak var buttonMessageDisplay: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
buttonMessageDisplay.text = ""
let button = UIButton(type:.Custom) as UIButton
button.frame = CGRectMake(0, 0, 100, 100)
button.center = CGPointMake(self.view.frame.size.width/2, self.view.frame.size.height/2);
button.backgroundColor = UIColor.redColor()
button.layer.borderColor = UIColor.blackColor().CGColor
button.layer.borderWidth = 3
button.layer.cornerRadius = 0.5 * button.bounds.size.width
button.setTitle("", forState: UIControlState.Normal)
button.addTarget(self, action: "buttonPressed", forControlEvents: .TouchUpInside)
self.view.addSubview(button)
}
var tapcount : Int = 0
let buttonMessage : [String] = [/* array of strings */]
#IBAction func userTap(sender: UITapGestureRecognizer) {
print("Tap Received")
if case 19...23 = tapcount {
buttonPressed()
}
}
func buttonPressed() {
switch tapcount {
case 0...18:
buttonMessageDisplay.text = buttonMessage[tapcount]
case 19...23:
//Hides the button
button.hidden = true
buttonMessageDisplay.text = buttonMessage[tapcount]
case 24...32:
//Unhides the button
button.hidden = false
buttonMessageDisplay.text = buttonMessage[tapcount]
case 33...100:
buttonMessageDisplay.text = buttonMessage[tapcount]
default:
print("There are no more messages or an error has been encountered")
}
print("Tap Count: \(tapcount)")
++tapcount
}
}
Your code makes no sense. As #formal says in his answer, you're creating a new button on every tap, which is wrong.
You want to define your button in your Storyboard.
Then you want an IBAction method, which takes the button as a parameter:
#IBAction func buttonPressed(sender: UIButton)
{
++tapcount
if tapcount < 19
{
sender.hidden = true
}
}
Note that if the button you're hiding is the same one the user is tapping, once it is hidden, you're done. The user can't tap a hidden button, so there's no way to un-hide it. (And thus no point in your switch statement)
Your main issue is that you are creating a new button every time you call button pressed. Create an #IBOutlet for your button and just set its hidden property in butPressed (which can be set as an action of the button).
Something like:
class ViewController: UIViewController {
#IBOutlet weak var button: UIButton!
#IBOutlet weak var buttonMessageDisplay: UILabel!
var tapcount = 0
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
#IBAction func butPressed(sender: AnyObject) {
switch tapcount {
case 19...23:
//Hides the button
button.hidden = true
case 24...31:
//Unhides the button
button.hidden = false
default: break
}
print("Tap Count: \(tapcount)")
buttonMessageDisplay.text = "Tap: \(tapcount)"
++tapcount
}
}
The method buttonPressed() creates a new button each time it is called. You should define button as a property similar to buttonMessageDisplay and place the code to initialise it within viewDidLoad().
You should give space between range in case condition:
For example:
(IBAction)buttonTapped:(id)sender {
self.count++;
switch (self.count) {
case 5 ... 23 :
self.button.titleLabel.text = #"disable";
self.button.hidden = true;
break;
default:
break;
}
}

How to pause a CADisplayLink?

I have a problem in my app. What I want to happened is when I click button2, it disappears and stops moving. What's happening now is that when I click button2, it disappears but doesn't stop moving (even while its hidden).Any help? Code:
#IBOutlet var label: UILabel!
#IBOutlet var label2: UILabel!
#IBOutlet var label3: UILabel!
#IBOutlet var button2: UIButton!
#IBAction func button3(sender: UIButton) {
label.hidden = false
button2.hidden = true
}
#IBOutlet var button4: UIButton!
#IBAction func button5(sender: UIButton) {
button4.hidden = true
label2.hidden = false
}
#IBAction func button1(sender: UIButton) {
label.hidden = true
label2.hidden = true
button2.hidden = false
button2.frame = CGRectMake(120, 400, 100, 100)
let displayLink = CADisplayLink(target: self, selector: "handleDisplayLink:")
displayLink.addToRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode)
let time = dispatch_time(dispatch_time_t(DISPATCH_TIME_NOW), 2 * Int64(NSEC_PER_SEC))
dispatch_after(time, dispatch_get_main_queue()) {
self.button4.hidden = false
self.button4.frame = CGRectMake(120, 400, 100, 100)
let displayLink1 = CADisplayLink(target: self, selector: "handleDisplayLink1:")
displayLink1.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()
label3.hidden = false
button2.hidden = true
}
}
func handleDisplayLink1(displayLink1: CADisplayLink) {
var button4Frame = button4.frame
button4Frame.origin.y += -2
button4.frame = button4Frame
if button4.frame.origin.y <= 50 {
displayLink1.invalidate()
label3.hidden = false
button4.hidden = true
}
}
override func viewDidLoad() {
super.viewDidLoad()
label.hidden = true
button2.hidden = true
label2.hidden = true
button4.hidden = true
label3.hidden = true
// Do any additional setup after loading the view, typically from a nib.
}
Thank you. Anton
This is exceedingly confusing because it's unclear as to the relationship between the two outlets called button2 and button4 and the #IBAction methods called button1, button3, and button5? How many buttons do you actually have? Two? Four? Five? And what these various labels? If they're not part of the question, they shouldn't be included in the code snippet.
But if it's button3 or button5 that's getting called, then yes, those hide the button, but don't invalidate the display link, so the display link will progress. If you want it to stop the display link, then you have to call invalidate:
var displayLink: CADisplayLink?
var displayLink1: CADisplayLink?
#IBAction func button3(sender: UIButton) {
label.hidden = false
button2.hidden = true
displayLink?.invalidate()
displayLink = nil
}
#IBAction func button5(sender: UIButton) {
button4.hidden = true
label2.hidden = false
displayLink1?.invalidate()
displayLink1 = nil
}
This obviously means that button5 should be using these properties, not using local variables for displayLink and displayLink1:
#IBAction func button1(sender: UIButton) {
label.hidden = true
label2.hidden = true
button2.hidden = false
button2.frame = CGRectMake(120, 400, 100, 100)
// NB: No `let` on the next line
displayLink = CADisplayLink(target: self, selector: "handleDisplayLink:")
displayLink?.addToRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode)
let time = dispatch_time(dispatch_time_t(DISPATCH_TIME_NOW), 2 * Int64(NSEC_PER_SEC))
dispatch_after(time, dispatch_get_main_queue()) {
self.button4.hidden = false
self.button4.frame = CGRectMake(120, 400, 100, 100)
// NB: No `let` on the next line
self.displayLink1 = CADisplayLink(target: self, selector: "handleDisplayLink1:")
self.displayLink1?.addToRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode)
}
}
A few additional observations:
If you're using auto-layout, you should be very wary about just adjusting the frame of these controls. In auto-layout, if you happen to do anything to trigger the constraints engine to be applied (e.g. update the text of these labels), the controls will move back to where the constraints defined them to be.
You're changing the frame of these controls, moving them 2 points per call. That's might end up with stuttering or changing of speeds of the animation UI as your app gets more complicated. You shouldn't update a fixed amount per call of the display link handler, but rather use some time based functions to determine how much time has elapsed and calculate the new coordinates from that.
I personally wouldn't use display links for this kind of stuff. I would just use the standard UIView.animateWithDuration, and then when I wanted to stop button2 from moving, I would:
let currentButton2Frame = button2.layer.presentationLayer()!.frame
button2.layer.removeAllAnimations()
button2.frame = currentButton2Frame
This identifies where it is, mid-animation, stops the animation, and resets the frame to the coordinates previously identified. This avoids the complexities of display links.
You need to store a reference to the display links you create in your button click handler. So create class level properties for them:
#IBAction func button5(sender: UIButton) {
button4.hidden = true
label2.hidden = false
}
// new code here:
var displayLink: CADisplayLink?
var displayLink1: CADisplayLink?
Then invalidate them before you create them, in case an instance already exists:
#IBAction func button1(sender: UIButton) {
displayLink?.invalidate()
displayLink1?.invalidate()
displayLink = CADisplayLink(target: self, selector: "handleDisplayLink:")
displayLink1 = CADisplayLink(target: self, selector: "handleDisplayLink1:")
displayLink =
label.hidden = true
label2.hidden = true
button2.hidden = false
// ... rest of your method
}

UITextView scrollview not moving when keyboard is shown

My app structure currently is a navigation controller that leads to viewcontrollers with scrollview embedded in them. The textfields, textview, buttons etc are on top of the scrollview. By using my current code, the scrollview moves the view when UITextFields are clicked, but not UITextViews. I have tried to adopt Apple's recommended method and tweaked it for UITextViews.
Also, in the function keyboardWillBeShown, the part that checks if active textfield/textview in hidden by the keyboard and scrolls, seems to not make any difference at all.
Where have I gone wrong? Thanks
class unwellBasic: UIViewController, UITextViewDelegate,
UIScrollViewDelegate, UITextFieldDelegate {
#IBOutlet var scrollView: UIScrollView!
weak var activeTextView : UITextView?
weak var activeTextField : UITextField?
#IBOutlet var main: UITextView!
#IBOutlet var initials: UITextField!
#IBOutlet var maleButton: UIButton!
#IBOutlet var femaleButton: UIButton!
#IBOutlet var age: UITextField!
#IBOutlet var test: UITextField!
func registerForKeyboardNotifications() {
let notificationCenter = NSNotificationCenter.defaultCenter()
notificationCenter.addObserver(self,
selector: "keyboardWillBeShown:",
name: UIKeyboardWillShowNotification,
object: nil)
notificationCenter.addObserver(self,
selector: "keyboardWillBeHidden:",
name: UIKeyboardWillHideNotification,
object: nil)
}
#IBAction func back(sender: AnyObject) {
self.navigationController?.popViewControllerAnimated(true)
}
#IBAction func next(sender: AnyObject) {
self.performSegueWithIdentifier("bodySegue", sender: self)
}
func tapped() {
initials.resignFirstResponder()
main.resignFirstResponder()
age.resignFirstResponder()
test.resignFirstResponder()
self.activeTextView = nil
}
// Called when the UIKeyboardDidShowNotification is sent.
func keyboardWillBeShown(sender: NSNotification) {
let info: NSDictionary = sender.userInfo!
let value: NSValue = info.valueForKey(UIKeyboardFrameEndUserInfoKey) as! NSValue
let keyboardSize: CGSize = value.CGRectValue().size
let contentInsets: UIEdgeInsets = UIEdgeInsetsMake(0.0, 0.0, keyboardSize.height, 0.0)
scrollView.contentInset = contentInsets
scrollView.scrollIndicatorInsets = contentInsets
// If active text field is hidden by keyboard, scroll it so it's visible
if self.activeTextView != nil {
var aRect: CGRect = self.view.frame
aRect.size.height -= keyboardSize.height
let activeTextViewRect: CGRect? = self.activeTextView!.frame
let activeTextViewOrigin: CGPoint? = activeTextViewRect?.origin
if (!CGRectContainsPoint(aRect, activeTextViewOrigin!)) {
scrollView.scrollRectToVisible(activeTextViewRect!, animated:true)
}
}
if self.activeTextField != nil {
var aRect: CGRect = self.view.frame
aRect.size.height -= keyboardSize.height
let activeTextViewRect: CGRect? = self.activeTextField!.frame
let activeTextViewOrigin: CGPoint? = activeTextViewRect?.origin
if (!CGRectContainsPoint(aRect, activeTextViewOrigin!)) {
scrollView.scrollRectToVisible(activeTextViewRect!, animated:true)
}
}
}
// Called when the UIKeyboardWillHideNotification is sent
func keyboardWillBeHidden(sender: NSNotification) {
let contentInsets: UIEdgeInsets = UIEdgeInsetsMake(0.0, 0.0, 0.0, 0.0)
scrollView.contentInset = contentInsets
self.scrollView .setContentOffset(CGPointMake(0, 0), animated: true)
self .viewDidLayoutSubviews()
self.activeTextView = nil
}
func textViewDidBeginEditing(textView: UITextView) {
self.activeTextView = textView
scrollView.scrollEnabled = true
}
func textViewDidEndEditing(textView: UITextView) {
self.activeTextView = nil
scrollView.scrollEnabled = false
self.scrollView .setContentOffset(CGPointMake(0, 0), animated: true)
self .viewDidLayoutSubviews()
}
func textFieldDidBeginEditing(textField: UITextField) {
activeTextField = textField
scrollView.scrollEnabled = true
}
func textFieldDidEndEditing(textField: UITextField) {
activeTextField = nil
scrollView.scrollEnabled = false
}
func textFieldShouldReturn(textField: UITextField) -> Bool // called when 'return' key pressed. return NO to ignore.
{
textField.resignFirstResponder()
return true;
}
override func viewDidLoad() {
super.viewDidLoad()
var tap = UITapGestureRecognizer (target: self, action: ("tapped"))
self.view.addGestureRecognizer(tap)
self.main.delegate = self
self.initials.delegate = self
self.age.delegate = self
self.registerForKeyboardNotifications()
}
inherit your scroll view with TPKeyboardAvoiding, you don't need to code too much

Resources