I am trying to create a table view such that I am able to play videos . I am able to do this using AVPlayer and layer.
I want to add a custom play and pause button with a slider to the bottom of a video view.
AVPlayerController comes built in with these controls.
How can I implement these in AVPlayer. I have been looking for examples. But I haven't found any.
Are there any GitHub examples or code samples that I can follow? Any help will be really appreciated.
here I add the points , you need to customize based on your need.
Step-1
initially hide your AVPlayer controls,
YourAVPlayerViewController.showsPlaybackControls = false
Step-2
create the structure like
one label for current Duration, One label for overall Duration, one UIbutton for pause and play your current player and one UISlider for seek The video.
step-3
initially close the simple steps.
first stop and play the player using button action , currentPlayer is your AVPlayer name.
#IBAction func handlePlayPauseButtonPressed(_ sender: UIButton) {
// sender.isSelected ? currentPlayer.pause() : currentPlayer.play()
if sender.isSelected {
currentPlayer.pause()
}
else {
currentPlayer.play()
}
}
second set the video duration, as like
let duration : CMTime = currentPlayer.currentItem!.asset.duration
let seconds : Float64 = CMTimeGetSeconds(duration)
lblOverallDuration.text = self.stringFromTimeInterval(interval: seconds)
third set the player current time to current duration label
let duration : CMTime = currentPlayer.currentTime()
let seconds : Float64 = CMTimeGetSeconds(duration)
lblcurrentText.text = self.stringFromTimeInterval(interval: seconds)
the following method is convert from NSTimeinterval to HH:MM:SS
func stringFromTimeInterval(interval: TimeInterval) -> String {
let interval = Int(interval)
let seconds = interval % 60
let minutes = (interval / 60) % 60
let hours = (interval / 3600)
return String(format: "%02d:%02d:%02d", hours, minutes, seconds)
}
finally we go for slider control for calulate the seek time
_playheadSlider.addTarget(self, action: #selector(self.handlePlayheadSliderTouchBegin), for: .touchDown)
_playheadSlider.addTarget(self, action: #selector(self.handlePlayheadSliderTouchEnd), for: .touchUpInside)
_playheadSlider.addTarget(self, action: #selector(self.handlePlayheadSliderTouchEnd), for: .touchUpOutside)
_playheadSlider.addTarget(self, action: #selector(self.handlePlayheadSliderValueChanged), for: .valueChanged)
lets we go for action, initially when touchbegin is start then stop the player
handlePlayheadSliderTouchBegin
#IBAction func handlePlayheadSliderTouchBegin(_ sender: UISlider) {
currentPlayer.pause()
}
set the current item label for calculate the sender.value * CMTimeGetSeconds(currentPlayer.currentItem.duration)
#IBAction func handlePlayheadSliderValueChanged(_ sender: UISlider) {
let duration : CMTime = currentPlayer.currentItem!.asset.duration
let seconds : Float64 = CMTimeGetSeconds(duration) * sender.value
// var newCurrentTime: TimeInterval = sender.value * CMTimeGetSeconds(currentPlayer.currentItem.duration)
lblcurrentText.text = self.stringFromTimeInterval(interval: seconds)
}
finally move the player based on seek
#IBAction func handlePlayheadSliderTouchEnd(_ sender: UISlider) {
let duration : CMTime = currentPlayer.currentItem!.asset.duration
var newCurrentTime: TimeInterval = sender.value * CMTimeGetSeconds(duration)
var seekToTime: CMTime = CMTimeMakeWithSeconds(newCurrentTime, 600)
currentPlayer.seek(toTime: seekToTime)
}
In addition to Anbu.Karthik's answer, the slider handing can be handled better in a single function like below:
slider.addTarget(self, action: #selector(self.handlePlayheadSliderValueChanged(sender:event:)), for: .valueChanged)
Slider Value Change:
Note switch cases handling all phases of slider event
#objc func handlePlayheadSliderValueChanged(sender: UISlider, event: UIEvent) {
if let duration: CMTime = player?.currentItem?.asset.duration {
let newCurrentTime: Float64 = CMTimeGetSeconds(duration) * Double(sender.value)
videoLengthLabel.text = self.stringFromTimeInterval(interval: newCurrentTime)
if let touchEvent = event.allTouches?.first {
switch (touchEvent.phase) {
case .began:
// on slider touch begin
pauseVideo()
break
case .moved:
// on slider movement
let seekToTime: CMTime = CMTimeMakeWithSeconds(newCurrentTime, preferredTimescale: 600)
player?.seek(to: seekToTime, completionHandler: { (completedSeek) in
// any additional operation upon seek completion
})
break
case .ended:
// on slider touch end (finger lift)
playVideo()
break
default:
break
}
}
}
}
Related
I want to detect number of taps
User can tap multiple times and I have to perform action based on number of taps.
I tried using UIButton with the below code but its detecting all the taps
if I tap three times, It prints
1
2
3
Code -
tapButton.addTarget(self, action: #selector(multipleTap(_:event:)), for: UIControl.Event.touchDownRepeat)
#objc func multipleTap(_ sender: UIButton, event: UIEvent) {
let touch: UITouch = event.allTouches!.first!
print(touch.tapCount)
}
I need the output to be just 3 if i tap three times.
Edit 1: for ex - if you tap three times in youtube it will forward 30 seconds and if you tap 4 times it will forward 40 secs.
You aren’t defining the problem clearly. Are you saying that you want to detect a group of repeated taps within a short time as a single, multi-tap event, and report the number of taps? If so, you need to add logic to do that. Decide how long to wait between taps before you consider the event complete.
You’ll then need to keep track of how many taps have occurred, and how long it’s been since the last tap. You’ll need a timer that fires when the inter-tap interval has passed with no new taps so that you can consider the event complete.
All this sounds a lot more like a tap gesture recognizer than a button. Tap gesture recoginizers are written to detect a specific number of taps, with timeouts and such, but don’t respond to variable numbers of taps. You might want to create a custom gesture recognizer that responds to a variable number of taps instead of a button.
introduce var to hold tap count
your modified code will be:
tapButton.addTarget(self, action: #selector(singleTap(_:)), for: .touchUpInside)
private var numberOfTaps = 0
private var lastTapDate: Date?
#objc private func singleTap(_ sender: UIButton) {
if let lastTapDate = lastTapDate, Date().timeIntervalSince(lastTapDate) <= 1 { // less then a second
numberOfTaps += 1
} else {
numberOfTaps = 0
}
lastTapDate = Date()
if numberOfTaps == 3 {
// do your rewind stuff here 30 sec
numberOfTaps = 0
}
}
edit: I'm not mind reader, but I guess you look for something like above (updated code)
class ViewController: UIViewController {
var tapCount: Int = 0
override func viewDidLoad() {
super.viewDidLoad()
tapButton.addTarget(self, action: #selector(multipleTap(sender:)), for: .touchUpInside)
}
#objc func multipleTap(sender: UIButton) {
tapCount += 1
if tapCount == 3 {
print(tapCount) //3
}
}
}
I'm not sure for what exactly you need this, but below Duncan's answer I read that you need to copy something from YouTube logic.
Yes, of course you can use tap gesture recognizer, but if you need from some reason UIButton, you can create its subclass.
After first touch nothing happens, but then after every next touch valueChanged handler which you will set in view controller gets called. If you don't press button again to certain wait duration, touches will be reset to 0.
class TouchableButton: UIButton {
var waitDuration: Double = 1.5 // change this for custom duration to reset
var valueChanged: (() -> Void)? // set this to handle press of button
var minimumTouches: Int = 2 // set this to change number of minimum presses
override init(frame: CGRect) {
super.init(frame: frame)
setTarget()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setTarget()
}
private func setTarget() {
addTarget(self, action: #selector(buttonTouched), for: .touchUpInside)
}
#objc private func buttonTouched() {
touches += 1
}
private var timer: Timer?
private var touches: Int = 0 {
didSet {
if touches >= minimumTouches {
valueChanged?()
timer?.invalidate()
timer = Timer.scheduledTimer(withTimeInterval: waitDuration, repeats: false) { _ in
self.touches = 0
}
}
}
}
}
then when you need to set what should happen after every touch, you can set value changed handler
button.valueChanged = { // button of type `TouchableButton`
//print("touched")
... // move 10s forward or backwards
}
you can also change waitDuration property which specify wait time between last press and time when touches will be reset
button.waitDuration = 1
also you can set number of minimum touches (first time when valueChanged gets executed)
button.minimumTouches = 3
You need to check time difference for touch events.
For ex: If button is pressed 4 times and in particular time, say 1 sec, if it does not get pressed again then you can print 4 taps otherwise don't print.
For this you also need to call the timer so that you can check the time of last event and print accordingly.
var timer : Timer?
var timeDuration = 2.0
var tapCount = 0
var lastButtonPressedTime : Date?
#IBAction func tapCountButtonAction(_ sender: UIButton) {
lastButtonPressedTime = Date()
if(tapCount == 0){
tapCount = 1
timer = Timer.scheduledTimer(withTimeInterval: timeDuration, repeats: true, block: { (timer) in
let difference = Calendar.current.dateComponents([.second], from:self.lastButtonPressedTime!, to:Date())
if(difference.second! > 1){
print(self.tapCount)
timer.invalidate()
self.tapCount = 0
}
})
}else{
tapCount += 1
}
}
The technique used here is introducing a time delay in executing the
final action
static let tapCount = 0
tapButton.addTarget(self, action: #selector(multipleTap(_:event:)), for: UIControl.Event.touchDownRepeat)
// An average user can tap about 7 times in 1 second using 1 finger
DispatchQueue.main.asyncAfter(deadLine: .now() + 1.0 /* We're waiting for a second till executing the target method to observe the total no of taps */ ) {
self.performAction(for: tapCount)
}
#objc func multipleTap(_ sender: UIButton, event: UIEvent) {
tapCount += 1
}
/// Perform the respective action based on the taps
private func performAction(for count: Int) {
// Resetting the taps after 1 second
tapCount = 0
switch (count){
case 2:
// Handle 2 clicks
case 3:
// Handle 3 clicks
case 4:
// Handle 4 clicks
default:
print("Action undefined for count: \(count)")
}
}
How can I count time on how long the button is pressed using UILongPressGestureRecognizer; I am looking to print the long-Pressed count time in the displayLabel.text
I've tried most possible way.
#IBOutlet weak var buttonPressed: UIButton!
#IBOutlet weak var displayLabel: UILabel!
var buttonPressedCount : Int = 0
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let longPressObj = UILongPressGestureRecognizer(target: self, action: #selector(longPressButton))
longPressObj.minimumPressDuration = 2
longPressObj.numberOfTouchesRequired = 1
self.view.addGestureRecognizer(longPressObj)
// below statement is not a right one but i tried many possiblity including this one.
if longPressObj.minimumPressDuration == TimeInterval(2) {
displayLabel.text = "Longpressed for 2 seconds"
} else if longPressObj.minimumPressDuration == TimeInterval(3) {
displayLabel.text = "3"
} else if longPressObj.minimumPressDuration == TimeInterval(3) {
displayLabel.text = "4"
}
}
#IBAction func longPressButton() {
displayLabel.text = "Button pressed for \(buttonPressedCount)"
}
I want to display time of the long-Pressed NOT BUTTON CLICKED.
enter image description here
Thank you in advance!
EDITS:-
1. I just want to show the Running duration while the user is performed Long-Press. I would really appreciate the real-time count in the
2. Also is it will be helpful to show the total duration after stop pressing.
(https://i.stack.imgur.com/ppr0W.png)
Your target function should include sender, then you can get the state of the UILongPressGestureRecognizer.
Here is the Apple official document.
First save the gesture begin time, then you can use the current time to subtract the begin pressed time to get the duration in state .ended (or/and .cancelled, .failed).
Sample code:
class ViewController: UIViewController {
var touchBeginTime: Double = 0
var touchCountTimer: Timer?
override func viewDidLoad() {
super.viewDidLoad()
let longPressObj = UILongPressGestureRecognizer(target: self, action: #selector(longPressButton(sender:)))
longPressObj.minimumPressDuration = 2
longPressObj.numberOfTouchesRequired = 1
self.view.addGestureRecognizer(longPressObj)
}
#IBAction func longPressButton(sender: UILongPressGestureRecognizer) {
switch sender.state {
case .began:
touchCountTimer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true, block: { (timer) in
print("User pressing \(Date().timeIntervalSince1970 - self.touchBeginTime) sec.")
})
touchBeginTime = Date().timeIntervalSince1970
break;
case .changed:
//print("Long pressed changed \(Date().timeIntervalSince1970 - touchBeginTime)")
break;
case .ended, .cancelled, .failed:
touchCountTimer?.invalidate()
print("User pressed total \(Date().timeIntervalSince1970 - touchBeginTime) sec.")
break;
default:
break;
}
}
}
Since you want to show the running duration while the long press is being performed, you need to use a timer.
Start the timer when the long press reaches the .began state and stop (invalidate) the timer when the long press reaches the .ended or .canceled state.
Your time should repeat every second and update the label based on the difference between the current date and the date the long press began.
Rather than using a long press gesture recognizer, why not attach button actions to the touchDownInside event and the touchUpInside and touchUpOutside events on your button. your touchDownInside code can start your timer, which would update the label. The touchUpInside/touchUpOutside action would stop the timer.
I'm trying to create an app that is similar to SnapChat when it takes videos. You are supposed to hold down a button to start taking a video and then release it when you are done. I'm trying to implement a timeout feature after 7 seconds and also a progress bar by using a Timer object. However, when I try fire the timer, it only calls the given selector once and that's it even though I tell it to repeat itself.
Here's my code to initialize the button:
let photoButton:UIButton = {
let but = UIButton(type: .custom)
but.layer.cornerRadius = 40
but.layer.borderColor = UIColor.white.cgColor
but.layer.borderWidth = 4
but.clipsToBounds = true
but.addTarget(self, action: #selector(takeVideo), for: .touchDown)
but.addTarget(self, action: #selector(stopVideo), for: [.touchUpInside, .touchUpOutside])
but.translatesAutoresizingMaskIntoConstraints = false
return but
}()
Here's the function takeVideo that is called when the user starts to hold down the button. I initialize and fire the Timer object here:
#objc func takeVideo() {
progressBar.isHidden = false
timer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(updateProgressbar), userInfo: nil, repeats: true)
startTime = Date().timeIntervalSince1970()
timer?.fire()
}
This is the function stopVideo where I invalidate the Timer object:
#objc func stopVideo() {
timer?.invalidate()
// stop video
}
And this is the updateProgressBar function:
#objc private func updateProgressbar() {
let maxLength = 7.0
let difference = Date().timeIntervalSince1970 - startTime!
progressBar.progress = Float(difference / maxLength)
if difference >= maxLength {
stopVideo() // Invalidates the timer and will stop video.
}
}
It seems that this person has had a similar issue, but when I tried to incorporate his answer of presenting the current view controller using async, it still doesn't work. Here's how I present the video recording view controller:
let recorder = RecorderViewController()
recorder.db = db
DispatchQueue.main.async(execute: {
self.present(recorder, animated: true, completion: nil)
})
Edit: I've fixed the incorrectness concerning the Timer object's fireDate property (the fix is also fixed in the above code). It fixed my problem.
I have a UITableViewCell with some buttons that have time values like the hours app. I would like to track the time on each cell whenever I click on the button related to that cell like the hours app does - as shown in the screen shot below.
I already know how deal with timers: The function below is used to update the time on a general label :
var startTime = TimeInterval()
var timer : Timer?
func updateTime() {
let currentTime = NSDate.timeIntervalSinceReferenceDate
//Find the difference between current time and start time.
var elapsedTime: TimeInterval = currentTime - startTime
//calculate the minutes in elapsed time.
let minutes = UInt8(elapsedTime / 60.0)
elapsedTime -= (TimeInterval(minutes) * 60)
//calculate the seconds in elapsed time.
let seconds = UInt8(elapsedTime)
elapsedTime -= TimeInterval(seconds)
//add the leading zero for minutes, seconds and millseconds and store them as string constants
let strMinutes = String(format: "%02d", minutes)
let strSeconds = String(format: "%02d", seconds)
//concatenate minuets, seconds and milliseconds as assign it to the UILabel
self.timeLabel?.text = "\(strMinutes):\(strSeconds)"
//labelShake(labelToAnimate: self.timeLabel!, bounceVelocity: 5.0, springBouncinessEffect: 8.0)
}
I can put the following code in ViewDidLoad to start the timer:
timer = Timer()
timer = Timer.scheduledTimer(timeInterval: 0.01, target: self, selector: #selector(updateTime), userInfo: nil, repeats: true)
startTime = NSDate.timeIntervalSinceReferenceDate
For tapping a button in a cell, I add a tag on the cell's button to track the cell that I tapped on as shown below
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
//...
cell.timerViewButton.tag = indexPath.row
cell.timerViewButton.addTarget(self, action: #selector(startTimerForCell), for: UIControlEvents.touchUpInside)
//...
return cell
}
// I can track the button clicked here.
func startTimerForCell(sender : UIButton) {
print("SelectedCell \(sender.tag)")
}
Can anyone help with how can I change the button title on the cell clicked to the do the counting and potential stopping the timer when I click on the button
From my understanding, your problem is want to track each timer states. Just keep in mind that UITableViewCell is reusable and it will keep change when you scroll or view it.
Normally I will use array to easily keep track all those states or values in cell.
So, in your controller will have class variables probably as below
struct Timer {
let start = false
let seconds = 0
// can have more properties as your need
}
var timers = [Timer]()
Then, in your start function startTimerForCell will keep monitor those array.
func startTimerForCell(sender : UIButton) {
let timer = timers[sender.tag]
if timer.start {
// stop the timer
timers[sender.tag].start = false
}
else {
// stop the timer
timers[sender.tag].start = true
}
}
I'm trying to make a Wack-a-Mole game. The way I am doing so is by having one button randomly appear and disappear around the screen after someone has tapped it. If they do not tap the button within one second after it reappears, then it will disappear and find a new position, then reappear and wait one second and repeat the above steps. However, whenever I run this code, it doesn't do that. It moves positions only if I get rid of the 'while' statement and the 'if else' statement. Why won't it loop, disappear, reappear, etc?
Delay is this: https://stackoverflow.com/a/24318861/5799228
#IBAction func moveButton(button: UIButton) {
while self.WaffleButton.hidden == true || false {
if self.WaffleButton.hidden == false {
self.WaffleButton.hidden = true
delay(3) {
// Find the button's width and height
let buttonWidth = button.frame.width
let buttonHeight = button.frame.height
// Find the width and height of the enclosing view
let viewWidth = button.superview!.bounds.width
let viewHeight = button.superview!.bounds.height
// Compute width and height of the area to contain the button's center
let xwidth = viewWidth - buttonWidth
let yheight = viewHeight - buttonHeight
// Generate a random x and y offset
let xoffset = CGFloat(arc4random_uniform(UInt32(xwidth)))
let yoffset = CGFloat(arc4random_uniform(UInt32(yheight)))
// Offset the button's center by the random offsets.
button.center.x = xoffset + buttonWidth / 2
button.center.y = yoffset + buttonHeight / 2
self.WaffleButton.hidden = false
self.delay(1) {
self.WaffleButton.hidden = true
}
}
} else { delay(3) {
// Find the button's width and height
let buttonWidth = button.frame.width
let buttonHeight = button.frame.height
// Find the width and height of the enclosing view
let viewWidth = button.superview!.bounds.width
let viewHeight = button.superview!.bounds.height
// Compute width and height of the area to contain the button's center
let xwidth = viewWidth - buttonWidth
let yheight = viewHeight - buttonHeight
// Generate a random x and y offset
let xoffset = CGFloat(arc4random_uniform(UInt32(xwidth)))
let yoffset = CGFloat(arc4random_uniform(UInt32(yheight)))
// Offset the button's center by the random offsets.
button.center.x = xoffset + buttonWidth / 2
button.center.y = yoffset + buttonHeight / 2
self.WaffleButton.hidden = false
self.delay(1) {
self.WaffleButton.hidden = true
}
}
}
}
}
You are using while in a way it is not meant to be used.
This is how you should use while :
var someCondition = true
while someCondition {
// this will loop as fast as possible untill someConditionIsTrue is no longer true
// inside the while statement you will do stuff x number of times
// then when ready you set someCondition to false
someCondition = false // stop
}
This is how you are using while :
let someConditionThatIsAlwaysTrue = true
while someConditionThatIsAlwaysTrue {
// condition is always true, so inifinite loop...
// this creates a function that is executed 3 seconds after the current looping pass of the while loop.
// while does not wait for it to be finished.
// while just keeps going.
// a fraction of a second later it will create another function that will execute 3 seconds later.
// so after 3 seconds an infite amount of functions will execute with a fraction of a second between them.
// except they won't, since the main thread is still busy with your infinite while loop.
delay(3) {
// stuff
}
}
How to do it the right way :
don't ever use while or repeat to "plan" delayed code execution.
Split up the problem in smaller problems:
Issue 1 : Creating a loop
A loop is created by have two functions that trigger each other.
I will call them execute and executeAgain.
So execute triggers executeAgain and executeAgain triggers execute and then it starts all over again -> Loop!
Instead of calling execute and executeAgain directly, you also create a start function. This is not needed but it is a good place to setup conditions for your looping function. start will call execute and start the loop.
To stop the loop you create a stop function that changes some condition.
execute and executeAgain will check for this condition and only keep on looping if the check is successful. stop makes this check fail.
var mustLoop : Bool = false
func startLoop() {
mustLoop = true
execute()
}
func execute() {
if mustLoop {
executeAgain()
}
}
func executeAgain() {
if mustLoop {
execute()
}
}
func stop() {
mustLoop = false
}
Issue 2: Delayed Execution
If you need a delay inside a subclass of NSObject the most obvious choice is NSTimer. Most UI classes (like UIButton and UIViewController) are subclasses of NSObject.
NSTimer can also be set to repeat. This would also create a loop that executes every x seconds. But since you actually have 2 alternating actions it makes more sense to adopt the more verbose looping pattern.
An NSTimer executes a function (passed as Selector("nameOfFunction")) after x amount of time.
var timer : NSTimer?
func planSomething() {
timer = NSTimer.scheduledTimerWithTimeInterval(3, target: self, selector: Selector("doSomething"), userInfo: nil, repeats: false)
}
func doSomething() {
// stuff
}
If you need a delay in another class/struct (or you don't like NSTimer) you can use the delay function that matt posted.
It will execute whatever you enter in the closure after x amount of time.
func planSomething() {
delay(3) {
doSomething()
}
}
func doSomething() {
// stuff
}
Combining the two solutions:
By using the loop pattern above you now have distinct functions. Instead of calling them directly to keep the loop going. You insert the delay method of your choice and pass the next function to it.
So NSTimer will have a Selector pointing to execute or executeAgain and with delay you place them in the closure
How to implement it elegantly:
I would subclass UIButton to implement all this. Then you can keep your UIViewController a lot cleaner. Just choose the subclass in IB and connect the IBOutlet as usual.
This subclass has a timer attribute that will replace your delay.
The button action wacked() is also set in the init method.
From your UIViewController you call the start() func of the button. This will start the timer.
The timer will trigger appear() or disappear.
wacked() will stop the timer and make the button hide.
class WackingButton : UIButton {
var timer : NSTimer?
var hiddenTime : NSTimeInterval = 3
var popUpTime : NSTimeInterval = 1
override init(frame: CGRect) {
super.init(frame: frame)
self.addTarget(self, action: "wacked", forControlEvents: UIControlEvents.TouchUpInside)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.addTarget(self, action: "wacked", forControlEvents: UIControlEvents.TouchUpInside)
}
func start() {
timer = NSTimer.scheduledTimerWithTimeInterval(hiddenTime, target: self, selector: Selector("appear"), userInfo: nil, repeats: false)
}
func appear() {
self.center = randomPosition()
self.hidden = false
timer?.invalidate()
timer = NSTimer.scheduledTimerWithTimeInterval(popUpTime, target: self, selector: Selector("dissappear"), userInfo: nil, repeats: false)
}
func dissappear() {
self.hidden = true
timer?.invalidate()
timer = NSTimer.scheduledTimerWithTimeInterval(hiddenTime, target: self, selector: Selector("appear"), userInfo: nil, repeats: false)
}
func wacked() {
self.hidden = true
timer?.invalidate()
}
func randomPosition() -> CGPoint {
// Find the width and height of the enclosing view
let viewWidth = self.superview?.bounds.width ?? 0 // not really correct, but only fails when there is no superview and then it doesn't matter anyway. Won't crash...
let viewHeight = self.superview?.bounds.height ?? 0
// Compute width and height of the area to contain the button's center
let xwidth = viewWidth - frame.width
let yheight = viewHeight - frame.height
// Generate a random x and y offset
let xoffset = CGFloat(arc4random_uniform(UInt32(xwidth)))
let yoffset = CGFloat(arc4random_uniform(UInt32(yheight)))
// Offset the button's center by the random offsets.
let x = xoffset + frame.width / 2
let y = yoffset + frame.height / 2
return CGPoint(x: x, y: y)
}
}
Your UIViewController :
class ViewController: UIViewController {
#IBOutlet weak var button1: WackingButton!
override func viewDidAppear(animated: Bool) {
button1.start()
}
}