Could not access custom view from UIViewController using Swift - ios

I am working credit card scanner using custom view. I have tried below code and could not able to access custom class. Could you please anyone help me to fix this issue. Attached error screenshot below. Thanks...
let BUTTON_SIZE = CGSize(width: 100, height: 20)
let BUTTON_BUFFER = 10
protocol GymCreditCardScannerViewDelegate: class {
func creditCardScannerDidCancel(_ scanner: Any?)
func creditCardScanner(_ scanner: Any?, scannedNumber number: String?)
}
import UIKit
class GymCreditCardScanner: UIView, CardIOViewDelegate {
weak var scannerdelegate: GymCreditCardScannerViewDelegate?
var cardIOView: CardIOView?
var cancelButton: UIButton?
var doneButton: UIButton?
var number = ""
var screenFrame = CGRect.zero
var innerFrame = CGRect.zero
func initIntoView(_ superView: UIView?, withDelegate delegate: Any?) -> Any? {
let frame = CGRect(x: 0, y: 0, width: superView?.frame.size.width ?? 0.0, height: superView?.frame.size.height ?? 0.0)
self.scannerdelegate = delegate as? GymCreditCardScannerViewDelegate
screenFrame = frame
let MARGIN: CGFloat = 0.05
innerFrame = CGRect(x: frame.size.width * MARGIN, y: frame.size.height * MARGIN, width: frame.size.width * (1 - 2 * MARGIN), height: frame.size.height * (1 - 2 * MARGIN))
cardIOView = CardIOView(frame: innerFrame)
cardIOView?.delegate = self as CardIOViewDelegate
cardIOView?.hideCardIOLogo = true
cardIOView?.scannedImageDuration = 0.1
addSubview(cardIOView!)
let buttonSize: CGSize = BUTTON_SIZE
cancelButton = UIButton(frame: CGRect(x: frame.size.width / 2 - buttonSize.width / 2, y: innerFrame.origin.y + innerFrame.size.height - buttonSize.height - CGFloat(BUTTON_BUFFER), width: buttonSize.width, height: buttonSize.height))
cancelButton?.setTitle("Cancel", for: .normal)
cancelButton?.addTarget(self, action: Selector(("handleCancelPress:")), for: .touchUpInside)
addSubview(cancelButton!)
superView?.addSubview(self)
return self
}
func remove() {
if (cardIOView != nil) {
cardIOView?.removeFromSuperview()
}
cardIOView = nil
removeFromSuperview()
}
func cardIOView(_ cardIOView: CardIOView!, didScanCard cardInfo: CardIOCreditCardInfo!) {
number = cardInfo.cardNumber
UIGraphicsBeginImageContextWithOptions(cardIOView.frame.size, true, 1)
if let aContext = UIGraphicsGetCurrentContext() {
cardIOView.layer.render(in: aContext)
}
let capture: UIImage? = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
let captureView = UIImageView(image: capture)
captureView.frame = cardIOView.frame
addSubview(captureView)
bringSubview(toFront: cancelButton!)
cardIOView.removeFromSuperview()
let frame: CGRect = screenFrame
let buttonSize: CGSize = BUTTON_SIZE
doneButton = UIButton(frame: CGRect(x: frame.size.width / 2 - buttonSize.width / 2, y: innerFrame.origin.y + innerFrame.size.height - buttonSize.height - CGFloat(BUTTON_BUFFER), width: buttonSize.width, height: buttonSize.height))
doneButton?.setTitle("Done", for: .normal)
doneButton?.addTarget(self, action: Selector(("handleDonePress:")), for: .touchUpInside)
addSubview(doneButton!)
doneButton?.alpha = 0
UIView.animate(withDuration: 0.075, delay: 0, options: .curveEaseOut, animations: {
self.doneButton?.alpha = 1
self.doneButton?.frame = CGRect(x: frame.size.width / 2 - buttonSize.width / 2 + (buttonSize.width + CGFloat(BUTTON_BUFFER)) / 2, y: self.innerFrame.origin.y + self.innerFrame.size.height - buttonSize.height - CGFloat(BUTTON_BUFFER), width: buttonSize.width, height: buttonSize.height)
self.cancelButton?.frame = CGRect(x: frame.size.width / 2 - buttonSize.width / 2 - (buttonSize.width + CGFloat(BUTTON_BUFFER)) / 2, y: self.innerFrame.origin.y + self.innerFrame.size.height - buttonSize.height - CGFloat(BUTTON_BUFFER), width: buttonSize.width, height: buttonSize.height)
}, completion: nil)
}
func handleCancelPress(_ sender: Any?) {
scannerdelegate?.creditCardScannerDidCancel(self)
}
func handleDonePress(_ sender: Any?) {
scannerdelegate?.creditCardScanner(self, scannedNumber: number)
}
}
My ViewContoller
class PaymentDetailsVC: UIViewController {
var scanner: GymCreditCardScanner?
//MARK: Credit card scanning
#IBAction func scanCreditCard(_ sender: Any) {
cardField.resignFirstResponder()
scanner = GymCreditCardScanner.initIntoView(view, withDelegate: self)
}
func creditCardScannerDidCancel(_ scanner: Any?) {
scanner.remove()
}
func creditCardScanner(_ scanner: Any?, scannedNumber number: String?) {
scanner.remove()
scanner = nil
numberField.text = number
}
}

Just replace var scanner: GymCreditCardScanner? // scanner is nil value you have to initial it
with
lazy var scanner: GymCreditCardScanner = GymCreditCardScanner()

You've written the function func initInto(_ superView: UIView?, withDelegate delegate: Any?) -> Any? { but instead you're trying to call the initIntoView.
function returns the Any? instead of GymCreditCardScanner. You can cast it like that:
scanner = GymCreditCardScanner.initInto(view, withDelegate: self) as? GymCreditCardScanner or simply change the function and return the GymCreditCardScanner? instead of Any?
func initInto should be static func initInto if you'd like to call it in this way or write your own constructor for this view
The scanner property is an optional (?), so you should call the remove() function like this:
scanner?.remove()

Your method declaration should be like this
static func initInto(_ superView: UIView?, withDelegate delegate: Any?) -> GymCreditCardScanner? {
//.......
let frame = CGRect(x: 0, y: 0, width: superView?.frame.size.width ?? 0.0, height: superView?.frame.size.height ?? 0.0)
let resultCreditCardScanner = GymCreditCardScanner(frame:frame)
resultCreditCardScanner.scannerdelegate = delegate as? GymCreditCardScannerViewDelegate
let MARGIN: CGFloat = 0.05
let innerFrame = CGRect(x: frame.size.width * MARGIN, y: frame.size.height * MARGIN, width: frame.size.width * (1 - 2 * MARGIN), height: frame.size.height * (1 - 2 * MARGIN))
resultCreditCardScanner.cardIOView = CardIOView(frame: innerFrame)
resultCreditCardScanner.cardIOView?.delegate = self as CardIOViewDelegate
resultCreditCardScanner.cardIOView?.hideCardIOLogo = true
resultCreditCardScanner.cardIOView?.scannedImageDuration = 0.1
resultCreditCardScanner.addSubview(cardIOView!)
let buttonSize: CGSize = BUTTON_SIZE
resultCreditCardScanner.cancelButton = UIButton(frame: CGRect(x: frame.size.width / 2 - buttonSize.width / 2, y: innerFrame.origin.y + innerFrame.size.height - buttonSize.height - CGFloat(BUTTON_BUFFER), width: buttonSize.width, height: buttonSize.height))
resultCreditCardScanner.cancelButton?.setTitle("Cancel", for: .normal)
resultCreditCardScanner.cancelButton?.addTarget(self, action: Selector(("handleCancelPress:")), for: .touchUpInside)
resultCreditCardScanner.addSubview(cancelButton!)
superView?.addSubview(resultCreditCardScanner)
return resultCreditCardScanner
}
then you will be able to do
scanner = GymCreditCardScanner.initInto(view, withDelegate: self)

Related

How to make waveform for my recorded audio?

I am using AVFoundation to record audio with the setting below.
After recording successfully, I need to show the waveform of the recorded file to the user. Can anyone help me with this task?
Here is my setting for recorder:
let recordSettings =
[AVNumberOfChannelsKey: 1,
AVFormatIDKey : kAudioFormatOpus,
AVSampleRateKey: 24000.0] as [String : Any]
import UIKit
import RYKit
let normalColor = UIColor.white
let normalAlphaColor = UIColor.init(white: 1.0, alpha: 0.5)
let highlightColor = UIColor.init(red: 163.0/255.0, green: 243.0/255.0, blue: 16.0/255.0, alpha: 1.0)
let highlightAlphaColor = UIColor.init(red: 163.0/255.0, green: 243.0/255.0, blue: 16.0/255.0, alpha: 0.24)
let waveWidth = CGFloat(2.5)
let waveSpace = CGFloat(0.5)
let waveRadius = CGFloat(1.25)
let upMaxHeight = CGFloat(60)
let downMaxHeight = CGFloat(30)
let upDownSpace = CGFloat(2)
protocol WaveformScrollDelegate: NSObjectProtocol {
func didScrollToTime(time: NSInteger)
func didScrollByPercentage(percent: Double, animated: Bool)
}
class WaveformComponent: UIView, CAAnimationDelegate, UIGestureRecognizerDelegate {
private var timeLine: UILabel!
private var topView: WaveformView!
private var topViewMask: CALayer!
private var bottomView: WaveformView!
private var isAnimated = false
private let convertTime = {
(seconds: Int) -> String in
let minute = seconds / 60
let minuteStr = minute > 9 ? "\(minute)" : "0\(minute)"
let second = seconds % 60
let secondStr = second > 9 ? "\(second)" : "0\(second)"
return "\(minuteStr):\(secondStr)"
}
var animationTimer: Timer!
weak var delegate: WaveformScrollDelegate?
var isVisible = true
/*
// Only override draw() if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
override func draw(_ rect: CGRect) {
// Drawing code
}
*/
required init?(coder: NSCoder) {
super.init(coder: coder)
}
init(frame: CGRect, amplitudes: [Double]) {
super.init(frame: frame)
self.backgroundColor = UIColor.clear
self.isOpaque = true
self.clipsToBounds = true
let width = (waveWidth + waveSpace) * CGFloat(amplitudes.count / 2)
let height = upMaxHeight + downMaxHeight + upDownSpace
let waveRect = CGRect.init(x: frame.size.width/2.0, y: (frame.size.height - height)/2.0, width: width, height: height)
bottomView = WaveformView.init(frame: waveRect, amplitudes: amplitudes, isHighlight: true)
self.addSubview(bottomView)
topView = WaveformView.init(frame: waveRect, amplitudes: amplitudes, isHighlight: false)
self.addSubview(topView)
topViewMask = CALayer()
topViewMask.frame = topView.bounds
topViewMask.backgroundColor = UIColor.white.cgColor
topView.layer.mask = topViewMask
timeLine = UILabel.init(frame: CGRect.init(x: (frame.size.width - 61.5)/2.0, y: (frame.size.height - upMaxHeight - upDownSpace - downMaxHeight)/2.0 + upMaxHeight - 19.0, width: 61.5, height: 19.0))
timeLine.backgroundColor = UIColor.init(red: 18/255.0, green: 18/255.0, blue: 18/255.0, alpha: 0.72)
timeLine.layer.cornerRadius = 9.5
timeLine.layer.masksToBounds = true
timeLine.textColor = UIColor.white
timeLine.font = UIFont.init(name: "PingFangSC-Regular", size: 8.0)
timeLine.textAlignment = .center
timeLine.text = "\(convertTime(0))/\(convertTime(amplitudes.count/2))"
self.addSubview(timeLine)
let panGesture = UIPanGestureRecognizer.init(target: self, action: #selector(handleGesture(gesture:)))
panGesture.delegate = self
addGestureRecognizer(panGesture)
isUserInteractionEnabled = true
}
func configureAmplitudes(amplitudes: [Double]) {
let width = (waveWidth + waveSpace) * CGFloat(amplitudes.count / 2)
let height = upMaxHeight + downMaxHeight + upDownSpace
self.topView.amplitudes = amplitudes
self.topView.frame = CGRect(x: screenw/2, y: 0, width: width, height: height)
self.topView.setNeedsDisplay()
topViewMask.frame = topView.bounds
self.bottomView.amplitudes = amplitudes
self.bottomView.frame = CGRect(x: screenw/2, y: 0, width: width, height: height)
self.bottomView.setNeedsDisplay()
}
func play() {
if !isAnimated {
isAnimated = true
topView.layer.add(keyframeAnimationFrom(topView.layer.position.x, to: (self.bounds.size.width - topView.layer.bounds.size.width)/2, isTop: false), forKey: "pan")
topViewMask.add(keyframeAnimationFrom(topViewMask.position.x, to: topViewMask.bounds.size.width*3/2, isTop: false), forKey: "pan")
bottomView.layer.add(keyframeAnimationFrom(bottomView.layer.position.x, to: (self.bounds.size.width - bottomView.layer.bounds.size.width)/2, isTop: false), forKey: "pan")
weak var weakSelf = self
animationTimer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true, block: { (timer) in
guard let presentation = weakSelf?.topView.layer.presentation() else { return }
let delta = (weakSelf!.bounds.size.width + weakSelf!.topView.bounds.size.width)/2 - presentation.position.x
weakSelf!.timeLine.text = "\(weakSelf!.convertTime(Int(round(delta / 3))))/\(weakSelf!.convertTime(weakSelf!.topView.amplitudes.count/2))"
if weakSelf!.delegate != nil {
let offset = delta / 3
let distance = weakSelf!.topView.amplitudes.count/2
if distance > 0 {
weakSelf!.delegate?.didScrollByPercentage(percent: Double(offset) / Double(distance), animated: true)
}else {
weakSelf!.delegate?.didScrollByPercentage(percent: 0, animated: true)
}
}
})
}
}
func pause() {
if isAnimated {
topView.layer.position = topView.layer.presentation()!.position
topViewMask.position = topViewMask.presentation()!.position
bottomView.layer.position = bottomView.layer.presentation()!.position
removeAnimate()
}
}
func reset() {
timeLine.text = "\(convertTime(0))/\(convertTime(topView.amplitudes.count/2))"
let position = CGPoint(x: (self.size.width + topView.size.width) / 2, y: self.size.height / 2)
topView.layer.position = position
topView.layer.removeAllAnimations()
topViewMask.position = CGPoint(x: topView.size.width / 2, y: topView.size.height / 2)
topViewMask.removeAllAnimations()
bottomView.layer.position = position
bottomView.layer.removeAllAnimations()
isAnimated = false
stopTimer()
}
func initialOffset(offset: Int) {
let position = CGPoint(x: (self.size.width + topView.size.width) / 2 - 3 * CGFloat(offset), y: self.size.height / 2)
topView.layer.position = position
topViewMask.position = CGPoint(x: topView.size.width / 2 + 3 * CGFloat(offset), y: topView.size.height / 2)
bottomView.layer.position = position
timeLine.text = "\(convertTime(offset))/\(convertTime(topView.amplitudes.count/2))"
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRequireFailureOf otherGestureRecognizer: UIGestureRecognizer) -> Bool {
if otherGestureRecognizer.isKind(of: UISwipeGestureRecognizer.self) {
let swipe = otherGestureRecognizer as! UISwipeGestureRecognizer
if (swipe.direction == .up || swipe.direction == .down) && ((swipe.qmui_targetView?.parentViewController?.isKind(of: AudioPlayerViewController.self)) != nil) {
return true
}
}
return false
}
// func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldBeRequiredToFailBy otherGestureRecognizer: UIGestureRecognizer) -> Bool {
// return true
// }
#objc private func handleGesture(gesture: UIPanGestureRecognizer) {
if gesture.state == .changed {
let translation = gesture.translation(in: self)
let absX = abs(translation.x)
let absY = abs(translation.y)
if (absX > absY ) {
if (translation.x < 0) {
//向左滑动
if isAnimated {
topView.layer.position = CGPoint.init(x: max(topView.layer.presentation()!.position.x + translation.x, (self.bounds.size.width - topView.layer.bounds.size.width)/2), y: topView.layer.position.y)
topViewMask.position = CGPoint.init(x: min(topViewMask.presentation()!.position.x - translation.x, topViewMask.bounds.size.width*3/2), y: topViewMask.position.y)
bottomView.layer.position = CGPoint.init(x: max(bottomView.layer.presentation()!.position.x + translation.x, (self.bounds.size.width - bottomView.layer.bounds.size.width)/2), y: bottomView.layer.position.y)
}else {
if topView.layer.frame.origin.x + topView.layer.frame.size.width <= self.bounds.size.width / 2 {
print("左滑,切歌下一曲")
return
}
topView.layer.position = CGPoint.init(x: max(topView.layer.position.x + translation.x, (self.bounds.size.width - topView.layer.bounds.size.width)/2), y: topView.layer.position.y)
topViewMask.position = CGPoint.init(x: min(topViewMask.position.x - translation.x, topViewMask.bounds.size.width*3/2), y: topViewMask.position.y)
bottomView.layer.position = CGPoint.init(x: max(bottomView.layer.position.x + translation.x, (self.bounds.size.width - bottomView.layer.bounds.size.width)/2), y: bottomView.layer.position.y)
}
gesture.setTranslation(CGPoint.zero, in: self)
}else{
//向右滑动
if isAnimated {
topView.layer.position = CGPoint.init(x: min(topView.layer.presentation()!.position.x + translation.x, (self.bounds.size.width + topView.layer.bounds.size.width)/2), y: topView.layer.position.y)
topViewMask.position = CGPoint.init(x: max(topViewMask.presentation()!.position.x - translation.x, topViewMask.bounds.size.width/2), y: topViewMask.position.y)
bottomView.layer.position = CGPoint.init(x: min(bottomView.layer.presentation()!.position.x + translation.x, (self.bounds.size.width + bottomView.layer.bounds.size.width)/2), y: bottomView.layer.position.y)
}else {
if topView.layer.frame.origin.x >= self.bounds.size.width / 2 {
print("右滑,切歌上一曲")
return
}
topView.layer.position = CGPoint.init(x: min(topView.layer.position.x + translation.x, (self.bounds.size.width + topView.layer.bounds.size.width)/2), y: topView.layer.position.y)
topViewMask.position = CGPoint.init(x: max(topViewMask.position.x - translation.x, topViewMask.bounds.size.width/2), y: topViewMask.position.y)
bottomView.layer.position = CGPoint.init(x: min(bottomView.layer.position.x + translation.x, (self.bounds.size.width + bottomView.layer.bounds.size.width)/2), y: bottomView.layer.position.y)
}
gesture.setTranslation(CGPoint.zero, in: self)
}
removeAnimate()
scrollTimeLineWhetherNotice(notice: false)
if delegate != nil {
let offset = (self.size.width + topView.size.width) / 2 - topView.layer.position.x
let distance = topView.size.width
delegate?.didScrollByPercentage(percent: Double(offset) / Double(distance), animated: false)
}
}
}
if gesture.state == .ended {
//考虑到歌曲存在缓冲,请手动调用play方法
// play()
scrollTimeLineWhetherNotice(notice: true)
}
}
private func scrollTimeLineWhetherNotice(notice: Bool) {
let delta = (self.bounds.size.width + self.topView.bounds.size.width)/2 - self.topView.layer.position.x
var time = NSInteger(round(delta / 3))
if time >= topView.amplitudes.count / 2 {
time = topView.amplitudes.count / 2 - 1
}
timeLine.text = "\(convertTime(time))/\(convertTime(topView.amplitudes.count/2))"
if delegate != nil && notice {
delegate?.didScrollToTime(time: time)
}
}
private func removeAnimate() {
if isAnimated {
isAnimated = false
topView.layer.removeAnimation(forKey: "pan")
topViewMask.removeAnimation(forKey: "pan")
bottomView.layer.removeAnimation(forKey: "pan")
}
}
private func keyframeAnimationFrom(_ start: CGFloat, to end: CGFloat, isTop:Bool) -> CAAnimation {
let animation = CAKeyframeAnimation.init(keyPath: "position.x")
let scale = UIScreen.main.scale
let increment = copysign(1, end - start) / scale
let numberOfSteps = Int(abs((end - start) / increment))
let positions = NSMutableArray.init(capacity: numberOfSteps)
for i in 0..<numberOfSteps {
positions.add(start + CGFloat(i) * increment)
}
animation.values = (positions as! [Any])
animation.calculationMode = .discrete
animation.isRemovedOnCompletion = false
animation.fillMode = .forwards
animation.duration = Double(Int(abs(end-start) / (AppConstants.waveWidth + AppConstants.waveSpace)))
animation.delegate = self
return animation
}
func animationDidStart(_ anim: CAAnimation) {
if anim == topView.layer.animation(forKey: "pan") {
}
}
func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
if UIApplication.shared.applicationState == .active && isVisible {
if isAnimated {
reset()
}
stopTimer()
}
}
private func stopTimer() {
guard let animationTimer = self.animationTimer else {
return
}
if animationTimer.isValid {
self.animationTimer.invalidate()
self.animationTimer = nil
}
}
deinit {
print("release WaveformComponent")
}
}
class WaveformView: UIView {
var isHighlight = false
var amplitudes = [Double]()
required init?(coder: NSCoder) {
super.init(coder: coder)
}
init(frame: CGRect, amplitudes: [Double], isHighlight: Bool) {
super.init(frame: frame)
self.backgroundColor = UIColor.clear
self.isOpaque = true
self.amplitudes = amplitudes
self.isHighlight = isHighlight
}
// Only override draw() if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
override func draw(_ rect: CGRect) {
// Drawing code
guard let context = UIGraphicsGetCurrentContext() else { return }
for i in 0..<amplitudes.count {
if i%2 == 0 {
//单数
let path = CGMutablePath()
let height = downMaxHeight * CGFloat(abs(amplitudes[i]))
path.addRoundedRect(in: CGRect.init(x: CGFloat(Int(i/2)) * (waveWidth + waveSpace), y: 62, width: 2.5, height: height), cornerWidth: 1.25, cornerHeight: 1.25 >= height/2.0 ? 0 : 1.25)
context.addPath(path)
if isHighlight {
context.setFillColor(highlightAlphaColor.cgColor)
}else {
context.setFillColor(normalAlphaColor.cgColor)
}
context.fillPath()
}else {
//双数
let path = CGMutablePath()
let height = upMaxHeight * CGFloat(abs(amplitudes[i]))
path.addRoundedRect(in: CGRect.init(x: CGFloat(Int(i/2)) * (waveWidth + waveSpace), y: 60 - height, width: 2.5, height: height), cornerWidth: 1.25, cornerHeight: 1.25 >= height/2.0 ? 0 : 1.25)
context.addPath(path)
if isHighlight {
context.setFillColor(highlightColor.cgColor)
}else {
context.setFillColor(normalColor.cgColor)
}
context.fillPath()
}
}
}
}
I don’t know if you want to create waveform from the scratch or custom, but there is a library called FDWaveformView and I have ever use this library in the past. After you install this library to your project, you can add a UIView which inherit FDWaveformView class, then provide the audio file.
Your code will likely look like this
import UIKit
import FDWaveformView
class ViewController: UIViewController {
#IBOutlet weak var mySampleWaveform: FDWaveformView!
override func viewDidLoad() {
super.viewDidLoad()
let thisBundle = Bundle(for: type(of: self))
let url = thisBundle.url(forResource: "myaudio", withExtension: "mp3")
mySampleWaveform.audioURL = url
mySampleWaveform.wavesColor = .green
mySampleWaveform.doesAllowScrubbing = true
mySampleWaveform.doesAllowStretch = true
mySampleWaveform.doesAllowScroll = true
}
}
it will show like this:
This will give you enough understanding about how does a waveform work, and you can custom many things such as color, width, height, etc.

Collection view cells overlap animation in Bottomsheet

I need to apply animation like the bottom sheet where Collection View cells overlap each other when closed, and wide opened when bottom sheet is opened. I have achieved the bottom sheet with Collection View with pagination, but need to apply animation that shows only current cell and other cells behind it when bottom sheet is closed.
Here is my Bottom Sheet Class, and some images as an example what I need to achieve:
class ScrollableBottomSheetViewController: UIViewController {
#IBOutlet var pageControl: UIPageControl!
#IBOutlet var collection: UICollectionView!
#IBOutlet var headerView: UIView!
let fullView: CGFloat = 0//50
var partialView: CGFloat {
return UIScreen.main.bounds.height - 50
}
let collectionMargin = CGFloat(32)
let itemSpacing = CGFloat(10)
let itemHeight = CGFloat(333)
var itemWidth = CGFloat(0)
var currentItem = 5
override func viewDidLoad() {
super.viewDidLoad()
self.definesPresentationContext = true
self.collection.delegate = self
self.collection.dataSource = self
self.collection.register(UINib(nibName:"AdsCollectionViewCell", bundle: nil), forCellWithReuseIdentifier: "default")
let gesture = UIPanGestureRecognizer.init(target: self, action: #selector(ScrollableBottomSheetViewController.panGesture))
gesture.delegate = self
view.addGestureRecognizer(gesture)
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
//CGFloat(219) //
itemWidth = UIScreen.main.bounds.width - collectionMargin * 2.0
print(itemWidth)
layout.sectionInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
layout.itemSize = CGSize(width: itemWidth, height: itemHeight)
layout.headerReferenceSize = CGSize(width: collectionMargin, height: 0)
layout.footerReferenceSize = CGSize(width: collectionMargin, height: 0)
layout.minimumLineSpacing = itemSpacing
layout.scrollDirection = .horizontal
collection!.collectionViewLayout = layout
collection?.decelerationRate = UIScrollViewDecelerationRateFast
}
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
let pageWidth = Float(itemWidth + itemSpacing)
let targetXContentOffset = Float(targetContentOffset.pointee.x)
let contentWidth = Float(collection!.contentSize.width )
var newPage = Float(self.pageControl.currentPage)
if velocity.x == 0 {
newPage = floor( (targetXContentOffset - Float(pageWidth) / 2) / Float(pageWidth)) + 1.0
} else {
newPage = Float(velocity.x > 0 ? self.pageControl.currentPage + 1 : self.pageControl.currentPage - 1)
if newPage < 0 {
newPage = 0
}
if (newPage > contentWidth / pageWidth) {
newPage = ceil(contentWidth / pageWidth) - 1.0
}
}
self.pageControl.currentPage = Int(newPage)
let point = CGPoint (x: CGFloat(newPage * pageWidth) + itemSpacing, y: targetContentOffset.pointee.y)
targetContentOffset.pointee = point
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
prepareBackgroundView()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
UIView.animate(withDuration: 0.6, animations: { [weak self] in
let frame = self?.view.frame
let yComponent = self?.partialView
self?.view.frame = CGRect(x: 0, y: yComponent!, width: frame!.width, height: frame!.height)// - 100)
})
}
#objc func panGesture(_ recognizer: UIPanGestureRecognizer) {
let translation = recognizer.translation(in: self.view)
let velocity = recognizer.velocity(in: self.view)
let y = self.view.frame.minY
if (y + translation.y >= fullView) && (y + translation.y <= partialView) {
self.view.frame = CGRect(x: 0, y: y + translation.y, width: view.frame.width, height: view.frame.height)
recognizer.setTranslation(CGPoint.zero, in: self.view)
}
if recognizer.state == .ended {
var duration = velocity.y < 0 ? Double((y - fullView) / -velocity.y) : Double((partialView - y) / velocity.y)
duration = duration > 1.3 ? 1 : duration
UIView.animate(withDuration: duration, delay: 0.0, options: [.allowUserInteraction], animations: {
if velocity.y >= 0 {
self.view.frame = CGRect(x: 0, y: self.partialView, width: self.view.frame.width, height: self.view.frame.height)
} else {
self.view.frame = CGRect(x: 0, y: self.fullView, width: self.view.frame.width, height: self.view.frame.height)
}
}, completion: { [weak self] _ in
if velocity.y < 0 {
self?.collection.isScrollEnabled = true
}
})
}
}
func prepareBackgroundView() {
let blurEffect = UIBlurEffect.init(style: .dark)
let visualEffect = UIVisualEffectView.init(effect: blurEffect)
let bluredView = UIVisualEffectView.init(effect: blurEffect)
bluredView.contentView.addSubview(visualEffect)
visualEffect.frame = UIScreen.main.bounds
bluredView.frame = UIScreen.main.bounds
view.insertSubview(bluredView, at: 0)
}
}
extension ScrollableBottomSheetViewController : UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 10
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "default", for: indexPath)
return cell
}
}
extension ScrollableBottomSheetViewController: UIGestureRecognizerDelegate {
// Solution
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
let gesture = (gestureRecognizer as! UIPanGestureRecognizer)
let direction = gesture.velocity(in: view).y
let y = view.frame.minY
if (y == fullView && collection.contentOffset.x == 0 && direction > 0) || (y == partialView) {
collection.isScrollEnabled = false
} else {
collection.isScrollEnabled = true
}
return false
}
}

SwiftPages updateUI Does Not Work with Swift 3

I'm using Swiftpages. When app is opened it looks like first picture.
But app goes to background and opened different app on device, after open again my app it looks like second picture.
I updated to Swift 3, but I can't figure out the issue, I write about it on Github but no reply from them.
public class SwiftPages: UIView {
private lazy var token = 0
var containerVieww: UIView!
private var scrollView: UIScrollView!
private var topBar: UIView!
var animatedBar: UIView!
var viewControllerIDs = [String]()
private var buttonTitles = [String]()
private var buttonImages = [UIImage]()
private var pageViews = [UIViewController?]()
private var currentPage: Int = 0
// Container view position variables
private var xOrigin: CGFloat = 0
private var yOrigin: CGFloat = 64
private var distanceToBottom: CGFloat = 0
// Color variables
private var animatedBarColor = UIColor(red: 28/255, green: 95/255, blue: 185/255, alpha: 1)
private var topBarBackground = UIColor.white
private var buttonsTextColor = UIColor.gray
private var containerViewBackground = UIColor.white
// Item size variables
private var topBarHeight: CGFloat = 52
private var animatedBarHeight: CGFloat = 3
// Bar item variables
private var aeroEffectInTopBar = false //This gives the top bap a blurred effect, also overlayes the it over the VC's
private var buttonsWithImages = false
var barShadow = true
private var shadowView : UIView!
private var shadowViewGradient = CAGradientLayer()
private var buttonsTextFontAndSize = UIFont(name: "HelveticaNeue-Light", size: 20)!
private var blurView : UIVisualEffectView!
private var barButtons = [UIButton?]()
// MARK: - Positions Of The Container View API -
public func setOriginX (origin : CGFloat) { xOrigin = origin }
public func setOriginY (origin : CGFloat) { yOrigin = origin }
public func setDistanceToBottom (distance : CGFloat) { distanceToBottom = distance }
// MARK: - API's -
public func setAnimatedBarColor (color : UIColor) { animatedBarColor = color }
public func setTopBarBackground (color : UIColor) { topBarBackground = color }
public func setButtonsTextColor (color : UIColor) { buttonsTextColor = color }
public func setContainerViewBackground (color : UIColor) { containerViewBackground = color }
public func setTopBarHeight (pointSize : CGFloat) { topBarHeight = pointSize}
public func setAnimatedBarHeight (pointSize : CGFloat) { animatedBarHeight = pointSize}
public func setButtonsTextFontAndSize (fontAndSize : UIFont) { buttonsTextFontAndSize = fontAndSize}
public func enableAeroEffectInTopBar (boolValue : Bool) { aeroEffectInTopBar = boolValue}
public func enableButtonsWithImages (boolValue : Bool) { buttonsWithImages = boolValue}
public func enableBarShadow (boolValue : Bool) { barShadow = boolValue}
override public func draw(_ rect: CGRect) {
DispatchQueue.main.async {
let pagesContainerHeight = self.frame.height - self.yOrigin - self.distanceToBottom
let pagesContainerWidth = self.frame.width
// Set the notifications for an orientation change & BG mode
let defaultNotificationCenter = NotificationCenter.default
defaultNotificationCenter.addObserver(self, selector: #selector(SwiftPages.applicationWillEnterBackground), name: NSNotification.Name.UIApplicationWillResignActive, object: nil)
defaultNotificationCenter.addObserver(self, selector: #selector(SwiftPages.orientationWillChange), name: NSNotification.Name.UIApplicationWillChangeStatusBarOrientation, object: nil)
defaultNotificationCenter.addObserver(self, selector: #selector(SwiftPages.orientationDidChange), name: NSNotification.Name.UIDeviceOrientationDidChange, object: nil)
defaultNotificationCenter.addObserver(self, selector: #selector(SwiftPages.applicationWillEnterForeground), name: NSNotification.Name.UIApplicationDidBecomeActive, object: nil)
// Set the containerView, every item is constructed relative to this view
self.containerVieww = UIView(frame: CGRect(x: self.xOrigin, y: self.yOrigin, width: pagesContainerWidth, height: pagesContainerHeight))
self.containerVieww.backgroundColor = self.containerViewBackground
self.containerVieww.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(self.containerVieww)
//Add the constraints to the containerView.
if #available(iOS 9.0, *) {
let horizontalConstraint = self.containerVieww.centerXAnchor.constraint(equalTo: self.centerXAnchor)
let verticalConstraint = self.containerVieww.centerYAnchor.constraint(equalTo: self.centerYAnchor)
let widthConstraint = self.containerVieww.widthAnchor.constraint(equalTo: self.widthAnchor)
let heightConstraint = self.containerVieww.heightAnchor.constraint(equalTo: self.heightAnchor)
NSLayoutConstraint.activate([horizontalConstraint, verticalConstraint, widthConstraint, heightConstraint])
}
// Set the scrollview
if self.aeroEffectInTopBar {
self.scrollView = UIScrollView(frame: CGRect(x: 0, y: 0, width: self.containerVieww.frame.size.width, height: self.containerVieww.frame.size.height))
} else {
self.scrollView = UIScrollView(frame: CGRect(x: 0, y: self.topBarHeight, width: self.containerVieww.frame.size.width, height: self.containerVieww.frame.size.height - self.topBarHeight))
}
self.scrollView.isPagingEnabled = true
self.scrollView.showsHorizontalScrollIndicator = false
self.scrollView.showsVerticalScrollIndicator = false
self.scrollView.delegate = self
self.scrollView.backgroundColor = UIColor.clear
self.scrollView.contentOffset = CGPoint(x: 0, y: 0)
self.scrollView.translatesAutoresizingMaskIntoConstraints = false
self.scrollView.isScrollEnabled = false
self.containerVieww.addSubview(self.scrollView)
// Add the constraints to the scrollview.
if #available(iOS 9.0, *) {
let leadingConstraint = self.scrollView.leadingAnchor.constraint(equalTo: self.containerVieww.leadingAnchor)
let trailingConstraint = self.scrollView.trailingAnchor.constraint(equalTo: self.containerVieww.trailingAnchor)
let topConstraint = self.scrollView.topAnchor.constraint(equalTo: self.containerVieww.topAnchor)
let bottomConstraint = self.scrollView.bottomAnchor.constraint(equalTo: self.containerVieww.bottomAnchor)
NSLayoutConstraint.activate([leadingConstraint, trailingConstraint, topConstraint, bottomConstraint])
}
// Set the top bar
self.topBar = UIView(frame: CGRect(x: 0, y: 0, width: self.containerVieww.frame.size.width, height: self.topBarHeight))
self.topBar.backgroundColor = self.topBarBackground
if self.aeroEffectInTopBar {
// Create the blurred visual effect
// You can choose between ExtraLight, Light and Dark
self.topBar.backgroundColor = UIColor.clear
let blurEffect: UIBlurEffect = UIBlurEffect(style: .light)
self.blurView = UIVisualEffectView(effect: blurEffect)
self.blurView.frame = self.topBar.bounds
self.blurView.translatesAutoresizingMaskIntoConstraints = false
self.topBar.addSubview(self.blurView)
}
self.topBar.translatesAutoresizingMaskIntoConstraints = false
self.containerVieww.addSubview(self.topBar)
// Set the top bar buttons
// Check to see if the top bar will be created with images ot text
if self.buttonsWithImages {
var buttonsXPosition: CGFloat = 0
for (index, image) in self.buttonImages.enumerated() {
let frame = CGRect(x: buttonsXPosition, y: 0, width: self.containerVieww.frame.size.width / CGFloat(self.viewControllerIDs.count), height: self.topBarHeight)
let barButton = UIButton(frame: frame)
barButton.backgroundColor = UIColor.clear
barButton.imageView?.contentMode = .scaleAspectFit
barButton.setImage(image, for: .normal)
barButton.tag = index
barButton.addTarget(self, action: #selector(SwiftPages.barButtonAction), for: .touchUpInside)
self.topBar.addSubview(barButton)
self.barButtons.append(barButton)
buttonsXPosition += self.containerVieww.frame.size.width / CGFloat(self.viewControllerIDs.count)
}
} else {
var buttonsXPosition: CGFloat = 0
for (index, title) in self.buttonTitles.enumerated() {
let frame = CGRect(x: buttonsXPosition, y: 0, width: self.containerVieww.frame.size.width / CGFloat(self.viewControllerIDs.count), height: self.topBarHeight)
let barButton = UIButton(frame: frame)
barButton.backgroundColor = UIColor.clear
barButton.titleLabel!.font = self.buttonsTextFontAndSize
barButton.setTitle(title, for: .normal)
barButton.setTitleColor(self.buttonsTextColor, for: .normal)
barButton.tag = index
barButton.addTarget(self, action: #selector(SwiftPages.barButtonAction), for: .touchUpInside)
self.topBar.addSubview(barButton)
self.barButtons.append(barButton)
buttonsXPosition += self.containerVieww.frame.size.width / CGFloat(self.viewControllerIDs.count)
}
}
// Set up the animated UIView
self.animatedBar = UIView(frame: CGRect(x: 0, y: self.topBarHeight - self.animatedBarHeight + 1, width: (self.containerVieww.frame.size.width / CGFloat(self.viewControllerIDs.count)) * 0.8, height: self.animatedBarHeight))
self.animatedBar.center.x = self.containerVieww.frame.size.width / CGFloat(self.viewControllerIDs.count << 1)
self.animatedBar.backgroundColor = self.animatedBarColor
self.containerVieww.addSubview(self.animatedBar)
// Add the bar shadow (set to true or false with the barShadow var)
if self.barShadow {
self.shadowView = UIView(frame: CGRect(x: 0, y: self.topBarHeight, width: self.containerVieww.frame.size.width, height: 4))
self.shadowViewGradient.frame = self.shadowView.bounds
self.shadowViewGradient.colors = [UIColor(red: 150/255, green: 150/255, blue: 150/255, alpha: 0.28).cgColor, UIColor.clear.cgColor]
self.shadowView.layer.insertSublayer(self.shadowViewGradient, at: 0)
self.containerVieww.addSubview(self.shadowView)
}
let pageCount = self.viewControllerIDs.count
// Fill the array containing the VC instances with nil objects as placeholders
for _ in 0..<pageCount {
self.pageViews.append(nil)
}
// Defining the content size of the scrollview
let pagesScrollViewSize = self.scrollView.frame.size
self.scrollView.contentSize = CGSize(width: pagesScrollViewSize.width * CGFloat(pageCount), height: pagesScrollViewSize.height)
// Load the pages to show initially
self.loadVisiblePages()
// Do the initial alignment of the subViews
self.alignSubviews()
}
}
// MARK: - Initialization Functions -
public func initializeWithVCIDsArrayAndButtonTitlesArray (VCIDsArray: [String], buttonTitlesArray: [String]) {
// Important - Titles Array must Have The Same Number Of Items As The viewControllerIDs Array
if VCIDsArray.count == buttonTitlesArray.count {
viewControllerIDs = VCIDsArray
buttonTitles = buttonTitlesArray
buttonsWithImages = false
} else {
print("Initilization failed, the VC ID array count does not match the button titles array count.")
}
}
public func initializeWithVCIDsArrayAndButtonImagesArray (VCIDsArray: [String], buttonImagesArray: [UIImage]) {
// Important - Images Array must Have The Same Number Of Items As The viewControllerIDs Array
if VCIDsArray.count == buttonImagesArray.count {
viewControllerIDs = VCIDsArray
buttonImages = buttonImagesArray
buttonsWithImages = true
} else {
print("Initilization failed, the VC ID array count does not match the button images array count.")
}
}
public func loadPage(page: Int) {
// If it's outside the range of what you have to display, then do nothing
guard page >= 0 && page < viewControllerIDs.count else { return }
// Do nothing if the view is already loaded.
guard pageViews[page] == nil else { return }
print("Loading Page \(page)")
// The pageView instance is nil, create the page
var frame = scrollView.bounds
frame.origin.x = frame.size.width * CGFloat(page)
frame.origin.y = 0.0
// Look for the VC by its identifier in the storyboard and add it to the scrollview
let newPageView = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: viewControllerIDs[page])
newPageView.view.frame = frame
scrollView.addSubview(newPageView.view)
// Replace the nil in the pageViews array with the VC just created
pageViews[page] = newPageView
}
public func loadVisiblePages() {
// First, determine which page is currently visible
let pageWidth = scrollView.frame.size.width
let page = Int(floor((scrollView.contentOffset.x * 2.0 + pageWidth) / (pageWidth * 2.0)))
// Work out which pages you want to load
let firstPage = page - 1
let lastPage = page + 1
// Load pages in our range
for index in firstPage...lastPage {
loadPage(page: index)
}
}
public func barButtonAction(sender: UIButton?) {
let index = sender!.tag
let pagesScrollViewSize = scrollView.frame.size
scrollView.setContentOffset(CGPoint(x: pagesScrollViewSize.width * CGFloat(index), y: 0), animated: true)
currentPage = index
}
// MARK: - Orientation Handling Functions -
public func alignSubviews() {
let pageCount = viewControllerIDs.count
// Setup the new frames
scrollView.contentSize = CGSize(width: CGFloat(pageCount) * scrollView.bounds.size.width, height: scrollView.bounds.size.height)
topBar.frame = CGRect(x: 0, y: 0, width: containerVieww.frame.size.width, height: topBarHeight)
blurView?.frame = topBar.bounds
animatedBar.frame.size = CGSize(width: (containerVieww.frame.size.width / (CGFloat)(viewControllerIDs.count)) * 0.8, height: animatedBarHeight)
if barShadow {
shadowView.frame.size = CGSize(width: containerVieww.frame.size.width, height: 4)
shadowViewGradient.frame = shadowView.bounds
}
// Set the new frame of the scrollview contents
for (index, controller) in pageViews.enumerated() {
controller?.view.frame = CGRect(x: CGFloat(index) * scrollView.bounds.size.width, y: 0, width: scrollView.bounds.size.width, height: scrollView.bounds.size.height)
}
// Set the new frame for the top bar buttons
var buttonsXPosition: CGFloat = 0
for button in barButtons {
button?.frame = CGRect(x: buttonsXPosition, y: 0, width: containerVieww.frame.size.width / CGFloat(viewControllerIDs.count), height: topBarHeight)
buttonsXPosition += containerVieww.frame.size.width / CGFloat(viewControllerIDs.count)
}
}
func applicationWillEnterBackground() {
//Save the current page
currentPage = Int(scrollView.contentOffset.x / scrollView.bounds.size.width)
print("Haydar")
}
func orientationWillChange() {
//Save the current page
currentPage = Int(scrollView.contentOffset.x / scrollView.bounds.size.width)
}
func orientationDidChange() {
//Update the view
alignSubviews()
scrollView.contentOffset = CGPoint(x: CGFloat(currentPage) * scrollView.frame.size.width, y: 0)
}
func applicationWillEnterForeground() {
alignSubviews()
scrollView.contentOffset = CGPoint(x: CGFloat(currentPage) * scrollView.frame.size.width, y: 0)
initializeWithVCIDsArrayAndButtonTitlesArray(VCIDsArray: buttonTitles, buttonTitlesArray: buttonTitles)
print("ForegroundHound")
}
public func scrollViewDidEndDecelerating(scrollView: UIScrollView) {
let previousPage : NSInteger = currentPage
let pageWidth : CGFloat = scrollView.frame.size.width
let fractionalPage = scrollView.contentOffset.x / pageWidth
let page : NSInteger = Int(round(fractionalPage))
if (previousPage != page) {
currentPage = page;
}
}
deinit {
NotificationCenter.default.removeObserver(self)
print("deinittta")
}
}
extension SwiftPages: UIScrollViewDelegate {
public func scrollViewDidScroll(_ scrollView: UIScrollView) {
// Load the pages that are now on screen
loadVisiblePages()
// The calculations for the animated bar's movements
// The offset addition is based on the width of the animated bar (button width times 0.8)
let offsetAddition = (containerVieww.frame.size.width / CGFloat(viewControllerIDs.count)) * 0.1
animatedBar.frame = CGRect(x: (offsetAddition + (scrollView.contentOffset.x / CGFloat(viewControllerIDs.count))), y: animatedBar.frame.origin.y, width: animatedBar.frame.size.width, height: animatedBar.frame.size.height)
}
}

UIImageView is moving back after new UIView is created

Hi I am trying to make a game using swift but am currently very stuck. My game has two buttons that move a UIImageView left and right which works perfectly although when my NSTimer calls to my animateBalls function every 2.5 seconds the UIImageView that was moved goes back to its original position. How would I be able to still be able to create a New UIView every 2.5 seconds but keep the UIIMageViews current position? Thanks for any help that you can give me, Here is my code:
import UIKit
class ViewController: UIViewController {
let speed = 10.0
#IBOutlet weak var mainBar: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
_ = NSTimer.scheduledTimerWithTimeInterval(2.5, target: self, selector: Selector("animateBalls"), userInfo: nil, repeats: true)
}
#IBAction func moveRightIsTapped(sender: AnyObject) {
if(mainBar.frame.origin.x < -158.5)
{
let xPosition = mainBar.frame.origin.x + 38.5
let yPosition = mainBar.frame.origin.y
let height = mainBar.frame.height
let width = mainBar.frame.width
mainBar.frame = CGRectMake(xPosition, yPosition, width, height)
}
}
#IBAction func moveLeftIsTapped(sender: AnyObject) {
if(mainBar.frame.origin.x > -389.5)
{
let xPosition = mainBar.frame.origin.x - 38.5
let yPosition = mainBar.frame.origin.y
let height = mainBar.frame.height
let width = mainBar.frame.width
mainBar.frame = CGRectMake(xPosition, yPosition, width, height)
}
}
#IBAction func playButtonTapped(sender: AnyObject) {
animateBalls()
}
func animateBalls() {
randomNum = Int(arc4random_uniform(1))
if(randomNum == 0)
{
let ball1 = UIView()
ball1.frame = CGRect(x: 121, y: -20, width: 20, height: 20)
ball1.layer.cornerRadius = 10
ball1.clipsToBounds = true
ball1.backgroundColor = UIColor.purpleColor()
self.view.addSubview(ball1)
UIView.animateWithDuration(speed, animations:{ ball1.frame = CGRect(x: 121, y: 705, width: ball1.frame.width, height: ball1.frame.height) })
}
if(randomNum == 1)
{
let ball2 = UIView()
ball2.frame = CGRect(x: 159, y: -20, width: 20, height: 20)
ball2.layer.cornerRadius = 10
ball2.clipsToBounds = true
ball2.backgroundColor = UIColor.greenColor()
self.view.addSubview(ball2)
UIView.animateWithDuration(speed, animations:{ ball2.frame = CGRect(x: 159, y: 705, width: ball2.frame.width, height: ball2.frame.height) })
}

How to implement range slider in Swift

I'm trying to implement Range Slider and I used custom control called NMRangeSlider.
But when I use it, the slider doesn't appear at all. Could it be also because it's all written in Objective-C?
This is how I've currently implemented it:
var rangeSlider = NMRangeSlider(frame: CGRectMake(16, 6, 275, 34))
rangeSlider.lowerValue = 0.54
rangeSlider.upperValue = 0.94
self.view.addSubview(rangeSlider)
To create a custom Range Slider I found a good solution here: range finder tutorial iOS 8 but I needed this in swift 3 for my project. I updated this for Swift 3 iOS 10 here:
in your main view controller add this to viewDidLayOut to show a range slider.
override func viewDidLayoutSubviews() {
let margin: CGFloat = 20.0
let width = view.bounds.width - 2.0 * margin
rangeSlider.frame = CGRect(x: margin, y: margin + topLayoutGuide.length + 170, width: width, height: 31.0)
}
create the helper function to print slider output below viewDidLayoutSubviews()
func rangeSliderValueChanged() { //rangeSlider: RangeSlider
print("Range slider value changed: \(rangeSlider.lowerValue) \(rangeSlider.upperValue) ")//(\(rangeSlider.lowerValue) \(rangeSlider.upperValue))
}
Create the file RangeSlider.swift and add this to it:
import UIKit
import QuartzCore
class RangeSlider: UIControl {
var minimumValue = 0.0
var maximumValue = 1.0
var lowerValue = 0.2
var upperValue = 0.8
let trackLayer = RangeSliderTrackLayer()//= CALayer() defined in RangeSliderTrackLayer.swift
let lowerThumbLayer = RangeSliderThumbLayer()//CALayer()
let upperThumbLayer = RangeSliderThumbLayer()//CALayer()
var previousLocation = CGPoint()
var trackTintColor = UIColor(white: 0.9, alpha: 1.0)
var trackHighlightTintColor = UIColor(red: 0.0, green: 0.45, blue: 0.94, alpha: 1.0)
var thumbTintColor = UIColor.white
var curvaceousness : CGFloat = 1.0
var thumbWidth: CGFloat {
return CGFloat(bounds.height)
}
override init(frame: CGRect) {
super.init(frame: frame)
trackLayer.rangeSlider = self
trackLayer.contentsScale = UIScreen.main.scale
layer.addSublayer(trackLayer)
lowerThumbLayer.rangeSlider = self
lowerThumbLayer.contentsScale = UIScreen.main.scale
layer.addSublayer(lowerThumbLayer)
upperThumbLayer.rangeSlider = self
upperThumbLayer.contentsScale = UIScreen.main.scale
layer.addSublayer(upperThumbLayer)
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
func updateLayerFrames() {
trackLayer.frame = bounds.insetBy(dx: 0.0, dy: bounds.height / 3)
trackLayer.setNeedsDisplay()
let lowerThumbCenter = CGFloat(positionForValue(value: lowerValue))
lowerThumbLayer.frame = CGRect(x: lowerThumbCenter - thumbWidth / 2.0, y: 0.0,
width: thumbWidth, height: thumbWidth)
lowerThumbLayer.setNeedsDisplay()
let upperThumbCenter = CGFloat(positionForValue(value: upperValue))
upperThumbLayer.frame = CGRect(x: upperThumbCenter - thumbWidth / 2.0, y: 0.0,
width: thumbWidth, height: thumbWidth)
upperThumbLayer.setNeedsDisplay()
}
func positionForValue(value: Double) -> Double {
return Double(bounds.width - thumbWidth) * (value - minimumValue) /
(maximumValue - minimumValue) + Double(thumbWidth / 2.0)
}
override var frame: CGRect {
didSet {
updateLayerFrames()
}
}
override func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
previousLocation = touch.location(in: self)
// Hit test the thumb layers
if lowerThumbLayer.frame.contains(previousLocation) {
lowerThumbLayer.highlighted = true
} else if upperThumbLayer.frame.contains(previousLocation) {
upperThumbLayer.highlighted = true
}
return lowerThumbLayer.highlighted || upperThumbLayer.highlighted
}
func boundValue(value: Double, toLowerValue lowerValue: Double, upperValue: Double) -> Double {
return min(max(value, lowerValue), upperValue)
}
override func continueTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
let location = touch.location(in: self)
// 1. Determine by how much the user has dragged
let deltaLocation = Double(location.x - previousLocation.x)
let deltaValue = (maximumValue - minimumValue) * deltaLocation / Double(bounds.width - thumbWidth)
previousLocation = location
// 2. Update the values
if lowerThumbLayer.highlighted {
lowerValue += deltaValue
lowerValue = boundValue(value: lowerValue, toLowerValue: minimumValue, upperValue: upperValue)
} else if upperThumbLayer.highlighted {
upperValue += deltaValue
upperValue = boundValue(value: upperValue, toLowerValue: lowerValue, upperValue: maximumValue)
}
// 3. Update the UI
CATransaction.begin()
CATransaction.setDisableActions(true)
updateLayerFrames()
CATransaction.commit()
sendActions(for: .valueChanged)
return true
}
override func endTracking(_ touch: UITouch?, with event: UIEvent?) {
lowerThumbLayer.highlighted = false
upperThumbLayer.highlighted = false
}
}
Next add the thumb layer subclass file RangeSliderThumbLayer.swift and add this to it:
import UIKit
class RangeSliderThumbLayer: CALayer {
var highlighted = false
weak var rangeSlider: RangeSlider?
override func draw(in ctx: CGContext) {
if let slider = rangeSlider {
let thumbFrame = bounds.insetBy(dx: 2.0, dy: 2.0)
let cornerRadius = thumbFrame.height * slider.curvaceousness / 2.0
let thumbPath = UIBezierPath(roundedRect: thumbFrame, cornerRadius: cornerRadius)
// Fill - with a subtle shadow
let shadowColor = UIColor.gray
ctx.setShadow(offset: CGSize(width: 0.0, height: 1.0), blur: 1.0, color: shadowColor.cgColor)
ctx.setFillColor(slider.thumbTintColor.cgColor)
ctx.addPath(thumbPath.cgPath)
ctx.fillPath()
// Outline
ctx.setStrokeColor(shadowColor.cgColor)
ctx.setLineWidth(0.5)
ctx.addPath(thumbPath.cgPath)
ctx.strokePath()
if highlighted {
ctx.setFillColor(UIColor(white: 0.0, alpha: 0.1).cgColor)
ctx.addPath(thumbPath.cgPath)
ctx.fillPath()
}
}
}
}
Finally add the track layer subclass file RangeSliderTrackLayer.swift and add the following to it:
import Foundation
import UIKit
import QuartzCore
class RangeSliderTrackLayer: CALayer {
weak var rangeSlider: RangeSlider?
override func draw(in ctx: CGContext) {
if let slider = rangeSlider {
// Clip
let cornerRadius = bounds.height * slider.curvaceousness / 2.0
let path = UIBezierPath(roundedRect: bounds, cornerRadius: cornerRadius)
ctx.addPath(path.cgPath)
// Fill the track
ctx.setFillColor(slider.trackTintColor.cgColor)
ctx.addPath(path.cgPath)
ctx.fillPath()
// Fill the highlighted range
ctx.setFillColor(slider.trackHighlightTintColor.cgColor)
let lowerValuePosition = CGFloat(slider.positionForValue(value: slider.lowerValue))
let upperValuePosition = CGFloat(slider.positionForValue(value: slider.upperValue))
let rect = CGRect(x: lowerValuePosition, y: 0.0, width: upperValuePosition - lowerValuePosition, height: bounds.height)
ctx.fill(rect)
}
}
}
Build Run and Get:
UPDATE:
It did not show to me, because it was all white. So the solution, without using any other framework and sticking with this one - you need to set all the views for all the components and then it will display well:
I have tried to import it in Swift as I used it before in Objective-C code, but without any luck. If I set everything properly and add it either in viewDidLoad() or viewDidAppear(), nothing gets displayed. One thing is worth mentioning, though - when I enter View Debug Hierarchy, the slider actually is there on the canvas:
But it's simply not rendered with all the colors that I did set before adding in it to the view. For the record - this is the code I used:
override func viewDidAppear(animated: Bool) {
var rangeSlider = NMRangeSlider(frame: CGRectMake(50, 50, 275, 34))
rangeSlider.lowerValue = 0.54
rangeSlider.upperValue = 0.94
let range = 10.0
let oneStep = 1.0 / range
let minRange: Float = 0.05
rangeSlider.minimumRange = minRange
let bgImage = UIView(frame: rangeSlider.frame)
bgImage.backgroundColor = .greenColor()
rangeSlider.trackImage = bgImage.pb_takeSnapshot()
let trackView = UIView(frame: CGRectMake(0, 0, rangeSlider.frame.size.width, 29))
trackView.backgroundColor = .whiteColor()
trackView.opaque = false
trackView.alpha = 0.3
rangeSlider.trackImage = UIImage(named: "")
let lowerThumb = UIView(frame: CGRectMake(0, 0, 8, 29))
lowerThumb.backgroundColor = .whiteColor()
let lowerThumbHigh = UIView(frame: CGRectMake(0, 0, 8, 29))
lowerThumbHigh.backgroundColor = UIColor.blueColor()
rangeSlider.lowerHandleImageNormal = lowerThumb.pb_takeSnapshot()
rangeSlider.lowerHandleImageHighlighted = lowerThumbHigh.pb_takeSnapshot()
rangeSlider.upperHandleImageNormal = lowerThumb.pb_takeSnapshot()
rangeSlider.upperHandleImageHighlighted = lowerThumbHigh.pb_takeSnapshot()
self.view.addSubview(rangeSlider)
self.view.backgroundColor = .lightGrayColor()
}
Using the method for capturing the UIView as UIImage mentioned in this question:
extension UIView {
func pb_takeSnapshot() -> UIImage {
UIGraphicsBeginImageContextWithOptions(bounds.size, false, UIScreen.mainScreen().scale)
drawViewHierarchyInRect(self.bounds, afterScreenUpdates: true)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image
}
}
Other solution:
You can also try sgwilly/RangeSlider instead, it's written in Swift and therefore you won't even need a Bridging Header.
try this code :
override func viewDidLayoutSubviews() {
let margin: CGFloat = 20.0
let width = view.bounds.width - 2.0 * margin
rangeSlider.frame = CGRect(x: margin, y: margin + topLayoutGuide.length,
width: width, height: 31.0)
}
I implemented the range slider using :
https://github.com/Zengzhihui/RangeSlider
In the GZRangeSlider class, there is a method called :
private func setLabelText()
In that method, just put :
leftTextLayer.frame = CGRectMake(leftHandleLayer.frame.minX - 0.5 * (kTextWidth - leftHandleLayer.frame.width), leftHandleLayer.frame.minY - kTextHeight, kTextWidth, kTextHeight)
rightTextLayer.frame = CGRectMake(rightHandleLayer.frame.minX - 0.5 * (kTextWidth - leftHandleLayer.frame.width), leftTextLayer.frame.minY, kTextWidth, kTextHeight)
to animate the lower and upper labels..
This one is working well for me and its in swift.. just try it..

Resources