Creating a snapping UISlider in Swift - ios

I am trying to create a snapping UISlider in Swift with a simple line at each snap, and the issue isn't the actual snapping part it is the subviews added. The subviews added look like garbage but I do not know how to make so that the subviews are equally spread out at the point where it would snap? If you look they are slightly off
protocol SnappingSliderDelegate {
func sliderChanged(_ value: Int)
}
class SnappingSlider: UISlider {
let step:CGFloat = 5
let start:CGFloat = 15
let end:CGFloat = 70
var delegate:SnappingSliderDelegate?
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}
override func awakeFromNib() {
setupView()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
func setupView(){
//self.setThumbImage(UIImage(named: "Play_black"), for: .normal)
self.minimumValue = Float(self.start)
self.maximumValue = Float(self.end)
for i in Int(start)...Int(end) where i % 5 == 0 {
let view = UIView(frame: CGRect(x: (Int(self.frame.width) / 11) * ((i - 15) / 5), y: 0, width: 1, height: 29))
view.backgroundColor = .lightGray
self.insertSubview(view, at: 0)
}
let view = UIView(frame: CGRect(x: self.frame.width, y: 0, width: 1, height: 29))
self.insertSubview(view, at: 0)
self.addTarget(self, action: #selector(valueChanged), for: .valueChanged)
}
#objc func valueChanged(_ sender: UISlider){
let step: Float = 5
let roundedValue = round(sender.value / step) * step
self.value = roundedValue
delegate?.sliderChanged(Int(roundedValue))
}
}

Related

Creating a haptic-touch-like button in Swift/Obj-C

I've been playing around with an idea for a button which, when held, expands to reveal other buttons (like the FAB in Android). Without releasing, sliding one's finger down should highlight other buttons, similar to haptic touch's behaviour.
Here's a crude mockup I've created using Drama: https://youtu.be/Iam8Gjv3gqM.
There should also be an option to have a label horizontally next to each button, and the order of buttons should be as shown (with the selected button at the top instead of its usual position).
I already have a class for each button (seen below), but do not know how to achieve this layout/behaviour.
class iDUButton: UIButton {
init(image: UIImage? = nil) {
super.init(frame: CGRect(x: 0, y: 0, width: 30, height: 30))
if let image = image {
setImage(image, for: .normal)
}
backgroundColor = .secondarySystemFill
layer.cornerRadius = 15
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
}
class iDUBadgeButton: iDUButton {
var badgeLabel = UILabel()
var badgeCount = 0
override init(image: UIImage? = nil) {
super.init(image: image)
setup()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setup()
}
private func setup() {
badgeLabel.textColor = .white
badgeLabel.backgroundColor = .systemRed
badgeLabel.textAlignment = .center
badgeLabel.font = .preferredFont(forTextStyle: .caption1)
badgeLabel.alpha = 0
updateBadge()
addSubview(badgeLabel)
}
override func layoutSubviews() {
super.layoutSubviews()
badgeLabel.sizeToFit()
let height = max(18, badgeLabel.frame.height + 5.0)
let width = max(height, badgeLabel.frame.width + 10.0)
badgeLabel.frame = CGRect(x: frame.width - 5, y: -badgeLabel.frame.height / 2, width: width, height: height);
badgeLabel.layer.cornerRadius = badgeLabel.frame.height / 2;
badgeLabel.layer.masksToBounds = true;
}
func setBadgeCount(badgeCount: Int) {
self.badgeCount = badgeCount;
self.updateBadge()
}
func updateBadge() {
if badgeCount != 0 {
badgeLabel.text = "\(badgeCount)"
}
UIView.animate(withDuration: 0.25, animations: {
self.badgeLabel.alpha = self.badgeCount == 0 ? 0 : 1
})
layoutSubviews()
}
}
If anyone could help with achieving this layout, I'd be very grateful! Thanks in advance.
PS: I'm developing the UI in Swift, but will be converting it to Objective-C once complete. If you're interested, the use case is to adapt the button in the top-left of this to offer a selection of different tags.

Background Colour in UIView not showing

I have a UIView that should show two colours, red and orange, based on the value of rating: Double The problem is that when I run the app nothing is showing up. In my output log the function prints that it has run and that the rating value is what it should be. So I am not sure why nothing is showing up when I run the app, I just see white.
class RatingViewController: UIView {
var rating: Double = 1.0
var rate: Double? {
didSet {
rating = rate!
setUpView()
}
}
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
private func setUpView() {
Self.backgroundcolor = UIColor.yellow
print("rating is \(rating), and width is \((UIScreen.main.bounds.width * CGFloat(rating/10)))")
let width = (UIScreen.main.bounds.width * CGFloat(rating/10))
var view: UIView
view = UIView(frame: CGRect(x: 0, y: 0, width: width, height: self.frame.size.height))
view.backgroundColor = UIColor.red
self.addSubview(view)
}
}
You have to add setUpView in init(: method
class RatingViewController: UIView {
var rating: Double = 1.0
required init?(coder: NSCoder) {
super.init(coder: coder)
}
init(frame: CGRect, rate: Double) {
super.init(frame: frame)
self.rating = rate
setUpView()
}
private func setUpView() {
self.backgroundColor = UIColor.yellow
print("rating is \(rating), and width is \((UIScreen.main.bounds.width * CGFloat(rating/10)))")
let width = (UIScreen.main.bounds.width * CGFloat(rating/10))
var view: UIView
view = UIView(frame: CGRect(x: 0, y: 0, width: width, height: self.frame.size.height))
view.backgroundColor = UIColor.red
self.addSubview(view)
}
}
Now you can call this class with custom init method like that:
// chnage frame and rate according to your requirment
let rView = RatingViewController(frame: CGRect(x: 0, y: 0, width: 320, height: 300), rate: 2.0)

iOS - add image and text to titleview with constraints

I am trying to make a custom title view for my nav bar with an image an some text next to it and this is how I did it thank to https://stackoverflow.com/a/47404105 :
class CustomTitleView: UIView
{
var title_label = CustomLabel()
var left_imageView = UIImageView()
override init(frame: CGRect){
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder){
super.init(coder: aDecoder)
setup()
}
func setup(){
self.addSubview(title_label)
self.addSubview(left_imageView)
}
func loadWith(title: String, leftImage: UIImage?)
{
title_label.text = title
title_label.font = UIFont.systemFont(ofSize: FontManager.fontSize + 5)
left_imageView.image = leftImage
setupFrames()
}
func setupFrames()
{
let height: CGFloat = 44
let image_size: CGFloat = height * 0.8
left_imageView.frame = CGRect(x: 0,
y: (height - image_size) / 2,
width: (left_imageView.image == nil) ? 0 : image_size,
height: image_size)
let titleWidth: CGFloat = title_label.intrinsicContentSize.width + 10
title_label.frame = CGRect(x: left_imageView.frame.maxX + 5,
y: 0,
width: titleWidth,
height: height)
contentWidth = Int(left_imageView.frame.width)
self.frame = CGRect(x: 0, y: 0, width: CGFloat(contentWidth), height: height)
}
var contentWidth: Int = 0
override func layoutSubviews() {
super.layoutSubviews()
self.frame.size.width = CGFloat(contentWidth)
}
But the problem is that I don't want to hardcode the frame, I want to use constraints but I have no idea where to start.
First take a imageView and set it's constraints according to your requirement and then take a Label besides that imageView set it's constraints too. Do this in your navigationBar where you want to show them.

Zoom on UIView contained in UIScrollView

I have some trouble handling zoom on UIScrollView that contains many subviews. I already saw many answers on SO, but none of them helped me.
So, basically what I'm doing is simple : I have a class called UIFreeView :
import UIKit
class UIFreeView: UIView, UIScrollViewDelegate {
var mainUIView: UIView!
var scrollView: UIScrollView!
var arrayOfView: [UIView]?
override init(frame: CGRect) {
super.init(frame: frame)
self.setupView()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
fileprivate func setupView() {
// MainUiView
self.mainUIView = UIView(frame: self.frame)
self.addSubview(mainUIView)
// ScrollView
self.scrollView = UIScrollView(frame: self.frame)
self.scrollView.delegate = self
self.addSubview(self.scrollView)
}
func reloadViews(postArray:[Post]?) {
if let postArray = postArray {
print("UIFreeView::reloadVIew.postArraySize = \(postArray.count)")
let size: CGFloat = 80.0
let margin: CGFloat = 20.0
scrollView.contentSize.width = (size * CGFloat(postArray.count))+(margin*CGFloat(postArray.count))
scrollView.contentSize.height = (size * CGFloat(postArray.count))+(margin*CGFloat(postArray.count))
for item in postArray {
let view = buildPostView(item)
self.scrollView.addSubview(view)
}
}
}
fileprivate func buildPostView(_ item:Post) -> UIView {
// Const
let size: CGFloat = 80.0
let margin: CGFloat = 5.0
// Var
let view = UIView()
let textView = UITextView()
let backgroundImageView = UIImageView()
// Setup view
let x = CGFloat(UInt64.random(lower: UInt64(0), upper: UInt64(self.scrollView.contentSize.width)))
let y = CGFloat(UInt64.random(lower: UInt64(0), upper: UInt64(self.scrollView.contentSize.height)))
view.frame = CGRect(x: x,
y: y,
width: size,
height: size)
// Setup background view
backgroundImageView.frame = CGRect(x: 0,
y: 0,
width: view.frame.size.width,
height: view.frame.size.height)
var bgName = ""
if (item.isFromCurrentUser) {
bgName = "post-it-orange"
} else {
bgName = "post-it-white"
}
backgroundImageView.contentMode = .scaleAspectFit
backgroundImageView.image = UIImage(named: bgName)
view.addSubview(backgroundImageView)
// Setup text view
textView.frame = CGRect(x: margin,
y: margin,
width: view.frame.size.width - margin*2,
height: view.frame.size.height - margin*2)
textView.backgroundColor = UIColor.clear
textView.text = item.content
textView.isEditable = false
textView.isSelectable = false
textView.isUserInteractionEnabled = false
view.addSubview(textView)
let gestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(handlePan))
view.addGestureRecognizer(gestureRecognizer)
return view
}
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return self.scrollView
}
func handlePan(_ gestureRecognizer: UIPanGestureRecognizer) {
if gestureRecognizer.state == .began || gestureRecognizer.state == .changed {
let translation = gestureRecognizer.translation(in: self.scrollView)
// note: 'view' is optional and need to be unwrapped
gestureRecognizer.view!.center = CGPoint(x: gestureRecognizer.view!.center.x + translation.x, y: gestureRecognizer.view!.center.y + translation.y)
gestureRecognizer.setTranslation(CGPoint.zero, in: self.scrollView)
}
}
/*
// Only override draw() if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
override func draw(_ rect: CGRect) {
// Drawing code
}
*/
}
This class is working perfectly, I can scroll through all my views, but I can't zoom-in and zoom-out in order to see less/more views on my screen.
I think the solution is simple, but I can't seem to find a real solution for my problem ..
Thanks !

TapGestureRecogniser not getting called when a subview

I have a view A that handles UITapGestureRecogniser. When it's on its own everything works great.
I have another View B that holds six of the View A objects. When I add View B to my ViewController the UITapGestureRecogniser stops working.
I have isUserInteractionEnabled = true on all the views.
Can anyone spot why it's not working?
How can I check if the tapGestures are being activated upon touch?
Thanks
Note: The ViewController doesn't have any UIGestureRecognisers on it
SingleView
class QTSingleSelectionView: UIView {
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
initialize()
}
override init(frame: CGRect) {
super.init(frame: frame)
initialize()
}
fileprivate func initialize() {
let tap = UITapGestureRecognizer(target: self, action: #selector(onTapListener))
addGestureRecognizer(tap)
}
func onTapListener() {
print("tap")
_isSelected.toggle()
setSelection(isSelected: _isSelected, animated: true)
}
}
Multiple views
class QTMutipleChoiceQuestionSelector: UIView {
var selectors: [QTSingleSelectionView] = []
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
initialize()
}
override init(frame: CGRect) {
super.init(frame: frame)
initialize()
}
fileprivate func initialize() {
for selector in selectors {
selector.removeFromSuperview()
}
selectors.removeAll()
guard let dataSource = dataSource else { return }
let count = dataSource.numberOfAnswers(self)
for index in 0..<count {
let selector = QTSingleSelectionView()
selector.frame = CGRect(x: 0, y: topMargin + heightOfSelector*CGFloat(index), width: frame.width, height: heightOfSelector)
selector.text = dataSource.mutipleChoiceQuestionSelector(self, textForAnswerAtIndex: index)
selector.selected = dataSource.mutipleChoiceQuestionSelector(self, isItemSelectedAtIndex: index)
selector.isUserInteractionEnabled = true
addSubview(selector)
}
}
}
ViewController
lazy var multipleChoiceView: QTMutipleChoiceQuestionSelector = {
let selector = QTMutipleChoiceQuestionSelector()
selector.dataSource = self
selector.isUserInteractionEnabled = true
return selector
}()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
view.addSubview(multipleChoiceView)
}
I have tested your code. The problem is in line:
let selector = QTMutipleChoiceQuestionSelector() //Wrong!
This line initializes the view with frame (0,0,0,0), that means it cannot receive any touch event, even if you could see it.
The solution is giving the view a size to receive events:
let selector = QTMutipleChoiceQuestionSelector(frame: CGRect(x: 0.0, y: 0.0, width: self.view.frame.width, height: self.view.frame.height))
Try This -
Instead of :
for index in 0..<count {
let selector = QTSingleSelectionView()
selector.frame = CGRect(x: 0, y: topMargin + heightOfSelector*CGFloat(index), width: frame.width, height: heightOfSelector)
selector.text = dataSource.mutipleChoiceQuestionSelector(self, textForAnswerAtIndex: index)
selector.selected = dataSource.mutipleChoiceQuestionSelector(self, isItemSelectedAtIndex: index)
selector.isUserInteractionEnabled = true
addSubview(selector)
}
You should use :
for index in 0..<count {
let aFrame: CGRect = CGRect(x: 0, y: topMargin + heightOfSelector*CGFloat(index), width: frame.width, height: heightOfSelector)
let selector = QTSingleSelectionView(frame: aFrame)
selector.text = dataSource.mutipleChoiceQuestionSelector(self, textForAnswerAtIndex: index)
selector.selected = dataSource.mutipleChoiceQuestionSelector(self, isItemSelectedAtIndex: index)
selector.isUserInteractionEnabled = true
addSubview(selector)
}
This will call the appropriate initializer which is init(frame: CGRect) which has the initializer() inside it. But right now your code call's default init() function of the UIView which does not have your custom initialize.

Resources