I customised a UISlider and everything works well except when I drag the slider to 100%. Then the rounded caps are replaced with a square.
Here is how I customize the Slider:
#IBInspectable var trackHeight: CGFloat = 14
override func trackRect(forBounds bounds: CGRect) -> CGRect {
return CGRect(origin: bounds.origin, size: CGSize(width: bounds.width, height: trackHeight))
}
98% image:
100% image:
You can remove the track background by set minimumTrackTintColor and maximumTrackTintColor into UIColor.clear and draw the track background yourself.
For example:
class CustomSlider: UISlider {
override func trackRect(forBounds bounds: CGRect) -> CGRect {
return bounds
}
override func draw(_ rect: CGRect) {
guard let context = UIGraphicsGetCurrentContext() else {
return
}
context.setFillColor(UIColor.green.cgColor)
let path = UIBezierPath(roundedRect: rect, cornerRadius: rect.size.height / 2).cgPath
context.addPath(path)
context.fillPath()
}
}
Here some quick solution
You have changed frame of the line, the height is higher the default and that's why you see it.
You can move little bit slider at the 100%.
override func thumbRect(forBounds bounds: CGRect, trackRect rect: CGRect, value: Float) -> CGRect {
var rect = super.thumbRect(forBounds: bounds, trackRect: rect, value: value)
if value > 0.99 {
rect = CGRect(x: rect.origin.x+2, y: rect.origin.y, width: rect.size.width, height: rect.size.height)
}
return rect
}
Or you can make the thumb bigger to cover the corners, its up to you.
Original answer here: Thumb image does not move to the edge of UISlider
updated to swift 4.2:
override func thumbRect(forBounds bounds: CGRect, trackRect rect: CGRect, value: Float) -> CGRect {
let unadjustedThumbrect = super.thumbRect(forBounds: bounds, trackRect: rect, value: value)
let thumbOffsetToApplyOnEachSide:CGFloat = unadjustedThumbrect.size.width / 2.0
let minOffsetToAdd = -thumbOffsetToApplyOnEachSide
let maxOffsetToAdd = thumbOffsetToApplyOnEachSide
let offsetForValue = minOffsetToAdd + (maxOffsetToAdd - minOffsetToAdd) * CGFloat(value / (self.maximumValue - self.minimumValue))
var origin = unadjustedThumbrect.origin
origin.x += offsetForValue
return CGRect(origin: origin, size: unadjustedThumbrect.size)
}
Related
I have use SkyFloatingLabelTextField and I want to vertically and horizontally center align text.
I have to write code but it aligns text only horizontally not vertically.
what I have tried is
var rightButton = UIButton(frame: CGRect(x: 0, y: 0, width: 25, height: 25))
var imageNew = UIImage(named: "rightArrow")!
rightButton.contentMode = .scaleAspectFit
rightButton.setImage(imageNew, for: .normal)
rightButton.setImage(imageNew, for: .selected)
rightButton.contentVerticalAlignment = .center
rightButton.contentHorizontalAlignment = .center
selectTradeTxt.rightView = rightButton
selectTradeTxt.rightView?.frame = self.selectTradeTxt.rightViewRect(forBounds: self.selectTradeTxt.bounds)
selectTradeTxt.rightViewMode = .always
selectTradeTxt.textColor = .white
selectTradeTxt.addRoundCorner(7)
// i have tried this
// selectTradeTxt.contentHorizontalAlignment = .center
selectTradeTxt.contentVerticalAlignment = .center
selectTradeTxt.textAlignment = .center
You have to change frame y position as bellow override function in SkyFloatingLabelTextField.swift file
// MARK: - UITextField text/placeholder positioning overrides
/**
Calculate the rectangle for the textfield when it is not being edited
- parameter bounds: The current bounds of the field
- returns: The rectangle that the textfield should render in
*/
override open func textRect(forBounds bounds: CGRect) -> CGRect {
let superRect = super.textRect(forBounds: bounds)
let titleHeight = self.titleHeight()
let rect = CGRect(
x: superRect.origin.x,
y: titleHeight + 2,
width: superRect.size.width,
height: superRect.size.height - titleHeight - selectedLineHeight
)
return rect
}
/**
Calculate the rectangle for the textfield when it is being edited
- parameter bounds: The current bounds of the field
- returns: The rectangle that the textfield should render in
*/
override open func editingRect(forBounds bounds: CGRect) -> CGRect {
let superRect = super.editingRect(forBounds: bounds)
let titleHeight = self.titleHeight()
let rect = CGRect(
x: superRect.origin.x,
y: titleHeight + 2,
width: superRect.size.width,
height: superRect.size.height - titleHeight - selectedLineHeight
)
return rect
}
/**
Calculate the rectangle for the placeholder
- parameter bounds: The current bounds of the placeholder
- returns: The rectangle that the placeholder should render in
*/
override open func placeholderRect(forBounds bounds: CGRect) -> CGRect {
let rect = CGRect(
x: 0,
y: self.isEditing ? titleHeight() : titleHeight() - 8,
width: bounds.size.width,
height: bounds.size.height - titleHeight() - selectedLineHeight
)
return rect
}
// MARK: - Positioning Overrides
/**
Calculate the bounds for the title label. Override to create a custom size title field.
- parameter bounds: The current bounds of the title
- parameter editing: True if the control is selected or highlighted
- returns: The rectangle that the title label should render in
*/
open func titleLabelRectForBounds(_ bounds: CGRect, editing: Bool) -> CGRect {
if editing {
return CGRect(x: 0, y: 10, width: bounds.size.width, height: titleHeight())
}
return CGRect(x: 0, y: titleHeight(), width: bounds.size.width, height: titleHeight())
}
I want to have the triangle on the right side of the textfield with a little padding, currently it is right at the border:
My code:
class Textfield: UITextField, UITextFieldDelegate {
func setup() {
let background = UIImage(systemName: "arrowtriangle.down.fill")
let imageView = UIImageView(image: background)
imageView.tintColor = .label
self.rightViewMode = .always
imageView.contentMode = .scaleAspectFit
self.rightView = imageView
}
}
How can I add insets so the triangle is a little bit more left?
You can override the text fields methods. Like this
class Textfield: UITextField, UITextFieldDelegate {
#IBInspectable var leftPadding : CGFloat = 0
#IBInspectable var rightPadding : CGFloat = 10
private var padding: UIEdgeInsets = UIEdgeInsets()
override func awakeFromNib() {
super.awakeFromNib()
padding = UIEdgeInsets(top: 0, left: leftPadding, bottom: 0, right: rightPadding)
setup()
}
func setup() {
let background = UIImage(systemName: "arrowtriangle.down.fill")
let imageView = UIImageView(image: background)
imageView.tintColor = .label
self.rightViewMode = .always
imageView.contentMode = .scaleAspectFit
self.rightView = imageView
}
override open func textRect(forBounds bounds: CGRect) -> CGRect {
return bounds.inset(by: padding)
}
override open func placeholderRect(forBounds bounds: CGRect) -> CGRect {
return bounds.inset(by: padding)
}
override open func editingRect(forBounds bounds: CGRect) -> CGRect {
return bounds.inset(by: padding)
}
override func rightViewRect(forBounds bounds: CGRect) -> CGRect {
return CGRect(x: bounds.width - 30, y: 0, width: 20 , height: bounds.height)
}
override func leftViewRect(forBounds bounds: CGRect) -> CGRect {
return CGRect(x: 10, y: 0, width: 20 , height: bounds.height)
}
}
This question already has answers here:
How to crop a UIImageView to a new UIImage in 'aspect fill' mode?
(2 answers)
Closed 2 years ago.
I'm trying to allow the user to mark an area with her hand and then the image will be cropped to that particular area. The problem is that i've got everything wrong with the cropping sizing, positioning and scaling.
I'm definitely missing something but am not sure what is it that I'm doing wrong?
Here is the original image along with the crop rectangle that the user can mark with his finger:
This is the broken outcome:
Here is my custom UIImageView where I intercept the touch events. This is just for the user to draw the rectangle...
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first?.preciseLocation(in: self){
self.newTouch = touch
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
let currentPoint = touch.preciseLocation(in: self)
reDrawSelectionArea(fromPoint: newTouch, toPoint: currentPoint)
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
self.newBoxSelected?(box.frame)
box.frame = CGRect.zero //reset overlay for next tap
}
func reDrawSelectionArea(fromPoint: CGPoint, toPoint: CGPoint) {
//Calculate rect from the original point and last known point
let rect = CGRect(x: min(fromPoint.x, toPoint.x),
y: min(fromPoint.y, toPoint.y),
width: abs(fromPoint.x - toPoint.x),
height: abs(fromPoint.y - toPoint.y));
box.frame = rect
}
This is the actual cropping logic. What am I doing wrong here?
func cropToBounds(image: UIImage, newFrame: CGRect) -> UIImage {
let xScaleFactor = (imageView.image?.size.width)! / (self.imageView.bounds.size.width)
let yScaleFactor = (imageView.image?.size.height)! / (self.imageView.bounds.size.height)
let contextImage: UIImage = UIImage(cgImage: image.cgImage!)
print("NewFrame is: \(newFrame)")
let xPos = newFrame.minX * xScaleFactor
let yPos = newFrame.minY * yScaleFactor
let width = newFrame.size.width * xScaleFactor
let height = newFrame.size.height * xScaleFactor
print("xScaleFactor: \(xScaleFactor)")
print("yScaleFactor: \(yScaleFactor)")
print("xPos: \(xPos)")
print("yPos: \(yPos)")
print("width: \(width)")
print("height: \(height)")
// let rect: CGRect = CGRect(x: xPos, y: yPos , width: width, height: height)
let rect: CGRect = CGRect(x: xPos, y: yPos , width: width, height: height)
// Create bitmap image from context using the rect
let imageRef: CGImage = contextImage.cgImage!.cropping(to: rect)!
// Create a new image based on the imageRef and rotate back to the original orientation
let image: UIImage = UIImage(cgImage: imageRef, scale: image.scale, orientation: image.imageOrientation)
return image
}
You can import AVFoundation and use func AVMakeRect(aspectRatio: CGSize, insideRect boundingRect: CGRect) -> CGRect to get the actual rectangle of the aspect-fit image:
// import this in your class
import AVFoundation
then:
guard let img = imageView.image else {
fatalError("imageView has no image!")
}
// Original size which you want to preserve the aspect ratio of
let aspect: CGSize = img.size
// Rect to fit that size within
let rect: CGRect = CGRect(x: 0, y: 0, width: imageView.bounds.size.width, height: imageView.bounds.size.height)
// resulting size
let resultSize: CGSize = AVMakeRect(aspectRatio: aspect, insideRect: rect).size
// get y-position (1/2 of (imageView height - resulting size)
let resultOrigin: CGPoint = CGPoint(x: 0.0, y: (imageView.bounds.size.height - resultSize.height) / 2.0)
// this is the actual rect for the aspect-fit image
let actualImageRect: CGRect = CGRect(origin: resultOrigin, size: resultSize)
print(actualImageRect)
// you now have the actual rectangle for the image
// on which you can base your scale calculations
}
I want to rotate a UIView with a drawing inside (circle, rectangle or line). The problem is when I rotate the view and refresh the drawing (I need ir when I change some properties of the drawing, i.e) the drawing doesn't follow the view...
UIView without rotation:
UIView with rotation:
Here is my simple code (little extract from the original code):
Main ViewController
class TestRotation: UIViewController {
// MARK: - Properties
var drawingView:DrawingView?
// MARK: - Actions
#IBAction func actionSliderRotation(_ sender: UISlider) {
let degrees = sender.value
let radians = CGFloat(degrees * Float.pi / 180)
drawingView?.transform = CGAffineTransform(rotationAngle: radians)
drawingView?.setNeedsDisplay()
}
// MARK: - Init
override func viewDidLoad() {
super.viewDidLoad()
drawingView = DrawingView(frame:CGRect(x:50, y:50, width: 100, height:75))
self.view.addSubview(drawingView!)
drawingView?.setNeedsDisplay()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
UIView
class DrawingView: UIView {
override func draw(_ rect: CGRect) {
let start = CGPoint(x: 0, y: 0)
let end = CGPoint(x: self.frame.size.width, y: self.frame.size.height)
drawCicrle(start: start, end: end)
}
func drawCicrle(start:CGPoint, end:CGPoint) {
//Contexto
let context = UIGraphicsGetCurrentContext()
if context != nil {
//Ancho
context!.setLineWidth(2)
//Color
context!.setStrokeColor(UIColor.black.cgColor)
context!.setFillColor(UIColor.orange.cgColor)
let rect = CGRect(origin: start, size: CGSize(width: end.x-start.x, height: end.y-start.y))
let path = UIBezierPath(ovalIn: rect)
path.lineWidth = 2
path.fill()
path.stroke()
}
}
// MARK: - Init
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = UIColor.gray
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
It seems that the problem is temporally solved if I don't refresh the view after rotating, but if I have to refresh later for some other reason (properties changed), the problem appears again.
What can I do? Thanks in advance
You are using the frame when you should be using the bounds.
The frame is the rectangle which contains the view. It is specified in the coordinate system of the superview of the view, and the frame changes when you rotate the view (because a rotated square extends further in the X and Y directions than a non-rotated square).
The bounds is the rectangle which contains the contents of the view. It is specified in the coordinate system of the view itself, and it doesn't change as the view is rotated or moved.
If you change frame to bounds in your draw(_:) function, it will work as you expect:
override func draw(_ rect: CGRect) {
let start = CGPoint(x: 0, y: 0)
let end = CGPoint(x: self.bounds.size.width, y: self.bounds.size.height)
drawCicrle(start: start, end: end)
}
Here's a demo showing the oval being redrawn as the slider moves.
Here's the changed code:
class DrawingView: UIView {
var fillColor = UIColor.orange {
didSet {
setNeedsDisplay()
}
}
override func draw(_ rect: CGRect) {
let start = CGPoint(x: 0, y: 0)
let end = CGPoint(x: self.bounds.size.width, y: self.bounds.size.height)
drawCircle(start: start, end: end)
}
func drawCircle(start:CGPoint, end:CGPoint) {
//Contexto
let context = UIGraphicsGetCurrentContext()
if context != nil {
//Ancho
context!.setLineWidth(2)
//Color
context!.setStrokeColor(UIColor.black.cgColor)
context!.setFillColor(fillColor.cgColor)
let rect = CGRect(origin: start, size: CGSize(width: end.x-start.x, height: end.y-start.y))
let path = UIBezierPath(ovalIn: rect)
path.lineWidth = 2
path.fill()
path.stroke()
}
}
// MARK: - Init
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = UIColor.gray
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
#IBAction func actionSliderRotation(_ sender: UISlider) {
let degrees = sender.value
let radians = CGFloat(degrees * Float.pi / 180)
drawingView?.transform = CGAffineTransform(rotationAngle: radians)
drawingView?.fillColor = UIColor(hue: CGFloat(degrees)/360.0, saturation: 1.0, brightness: 1.0, alpha: 1.0)
}
As you can see from the following screenshot
if I approach too much to the border of the view to magnify (which is a UIView containing the UIImageView) then I get that annoying and strange effect. So I have tried to set the whole UIViewController.view as the view to magnify and
in effect that effect disappears but look what happens instead:
It seems that the magnified zone showed in the magnifier circle does not correspond anymore to the one where I am tapping (with the cursor of the mouse). I would like the magnifier to be fixed in a zone of the screen that I will calculate according to the position where the user taps; for the moment I put it at the center. This is my class of the magnifier
import UIKit
public class MagnifyingGlassView: UIView {
var viewToMagnify: UIView?
var scale: CGFloat = 2.0
var zoomedPoint:CGPoint?
var size: CGFloat = 200.0 {
didSet {
let c = center
frame = CGRectMake(c.x - size / 2, c.y - size / 2, size, size)
}
}
var outlineColor: UIColor? {
didSet {
layer.borderColor = outlineColor?.CGColor
}
}
var outlineWidth: CGFloat? {
didSet {
layer.borderWidth = outlineWidth ?? 0.0
}
}
var touchPoint: CGPoint = CGPointZero {
didSet {
self.center = touchPoint
}
}
required public init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
commonInit()
}
required public override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
public init() {
super.init(frame: CGRectMake(0, 0, size, size))
commonInit()
}
public override func drawRect(rect: CGRect) {
guard let context = UIGraphicsGetCurrentContext()
else { return }
CGContextTranslateCTM(context, frame.size.width / 2, frame.size.height / 2)
CGContextScaleCTM(context, scale, scale)
CGContextTranslateCTM(context, -zoomedPoint!.x, -zoomedPoint!.y)
hidden = true
viewToMagnify?.layer.renderInContext(context)
/* color of axes */
CGContextSetRGBStrokeColor(context, 1.0, 0.0, 0.0, 1.0)
/* line width of axes */
CGContextSetLineWidth(context, 0.5)
/* draw vertical axis inside magnifier */
CGContextMoveToPoint(context, self.zoomedPoint!.x , self.zoomedPoint!.y - (self.frame.size.height / 2))
CGContextAddLineToPoint(context, self.zoomedPoint!.x, self.zoomedPoint!.y + (self.frame.size.height / 2))
/* draw horizontal axis inside magnifier */
CGContextMoveToPoint(context, self.zoomedPoint!.x - (self.frame.size.width / 2), self.zoomedPoint!.y)
CGContextAddLineToPoint(context, self.zoomedPoint!.x + (self.frame.size.width / 2), self.zoomedPoint!.y)
CGContextStrokePath(context)
hidden = false
}
private func commonInit() {
layer.cornerRadius = frame.size.width / 2
layer.masksToBounds = true
}
}
where zoomedPoint is the point in the screen where the user is tapping. Do you know how to correct this?