UIView frame slightly different when setting corner radius - ios

I am drawing a sequence of UIViews that touch each other's edges.
But when I set view.layer.cornerRadius they get slightly out of alignment creating artifacts. Is there some way around this?
Minimal working example (Swift Playround):
It seems to depend on the floating point coordinates.
I'd prefer if there was a version that didn't require me to round the coordinates to whole integers, though.
import UIKit
import PlaygroundSupport
class ViewController: UIViewController {
override func viewDidLoad(){
super.viewDidLoad()
self.view.backgroundColor = .white
let rect11 = UIView(frame: CGRect(x: 100, y: 100.15, width: 100, height: 100))
let rect12 = UIView(frame: CGRect(x: 100, y: 200.15, width: 100, height: 100))
let rect21 = UIView(frame: CGRect(x: 300, y: 100.15, width: 100, height: 100))
let rect22 = UIView(frame: CGRect(x: 300, y: 200.15, width: 100, height: 100))
rect21.layer.cornerRadius = 10
rect22.layer.cornerRadius = 10
rect11.backgroundColor = .black
rect12.backgroundColor = .black
rect21.backgroundColor = .black
rect22.backgroundColor = .black
self.view.addSubview(rect11)
self.view.addSubview(rect12)
self.view.addSubview(rect21)
self.view.addSubview(rect22)
}
}
let controller = ViewController()
PlaygroundPage.current.liveView = UINavigationController(rootViewController: controller)

Related

How to specify which child view does not inherit shadows when setting shadows for the parent view?

I want bView to be free of shadows. How can I achieve this?
I want to shadow all subviews of the superView, so I cannot remove the shadow settings of the superView.
let superView: UIView = UIView(frame: CGRect(x: 0, y: 0, width: self.view.frame.size.width, height: self.view.frame.size.height))
superView.layer.shadowColor = UIColor.lightGray.cgColor
superView.layer.shadowOffset = CGSize(width: 0, height: 3)
superView.layer.shadowOpacity = 0.2
superView.layer.shadowRadius = 5
self.view.addSubview(superView)
let aView: UIView = UIView(frame: CGRect(x: 10, y: 100, width: 100, height: 100))
aView.backgroundColor = .white
superView.addSubview(aView)
let bView: UIView = UIView(frame: CGRect(x: 10, y: 230, width: 100, height: 100))
bView.backgroundColor = .white
superView.addSubview(bView)
I have tried bView.clipsToBounds = true but it's not working.
If you give superView a non-clear background color then none of its subviews will get a shadow. You can then apply a shadow to the subviews that you want to have a shadow.
let superView: UIView = UIView(frame: CGRect(x: 0, y: 0, width: 300, height: 350))
superView.backgroundColor = .white // <-- Add this line
superView.layer.shadowColor = UIColor.black.cgColor
superView.layer.shadowOffset = CGSize(width: 0, height: 3)
superView.layer.shadowOpacity = 0.2
superView.layer.shadowRadius = 5
view.addSubview(superView)
let aView: UIView = UIView(frame: CGRect(x: 10, y: 100, width: 100, height: 100))
aView.backgroundColor = .white
// Add shadow to aView
aView.layer.shadowColor = UIColor.black.cgColor
aView.layer.shadowOffset = CGSize(width: 0, height: 3)
aView.layer.shadowOpacity = 0.2
aView.layer.shadowRadius = 5
superView.addSubview(aView)
let bView: UIView = UIView(frame: CGRect(x: 10, y: 230, width: 100, height: 100))
bView.backgroundColor = .white
superView.addSubview(bView)
If you have several views that need a shadow then I would define a method to make it easier:
extension UIView {
func addShadow() {
layer.shadowColor = UIColor.black.cgColor
layer.shadowOffset = CGSize(width: 0, height: 3)
layer.shadowOpacity = 0.2
layer.shadowRadius = 5
}
}
Then your code becomes something like this:
let superView: UIView = UIView(frame: CGRect(x: 0, y: 0, width: 300, height: 350))
superView.backgroundColor = .white
superView.addShadow()
view.addSubview(superView)
let aView: UIView = UIView(frame: CGRect(x: 10, y: 100, width: 100, height: 100))
aView.backgroundColor = .white
aView.addShadow()
superView.addSubview(aView)
let bView: UIView = UIView(frame: CGRect(x: 10, y: 230, width: 100, height: 100))
bView.backgroundColor = .white
superView.addSubview(bView)

How to re-draw line connected between moveable two UIView

I would like to re-draw the line between two movable UIView, depending on the position of UIView.
So I found this. However, this method uses "Pan Gesture" to re-draw lines, depending on the position of the gesture.
Also found are setNeedsDisplay(), but this is a request to re-draw, not a real-time event function.
The way I want to find it is not to use gestures to redraw lines, but to re-draw lines in real time.
In a little bit more detail, I applied "UIColisionBehavior" to all the UIVviews I created. The UIView applied changes position as they are struck, and depending on the changed position, the line is being redrawn.
As if the UIView were moving in this way, the connected line was redrawn according to where it was moved:
Below is the code I'm experimenting with in the Playground. When you execute the code, you can see that the first connected purple line is connected to the falling UIView and does not fall off:
//: A UIKit based Playground for presenting user interface
import UIKit
import PlaygroundSupport
class MoveAbleView : UIView {
var outGoingLine : CAShapeLayer?
var inComingLine : CAShapeLayer?
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func lineTo(connectedView: MoveAbleView) -> CAShapeLayer {
let path = UIBezierPath()
path.move(to: self.center)
path.addLine(to: connectedView.center)
let line = CAShapeLayer()
line.path = path.cgPath
line.lineWidth = 5
line.strokeColor = UIColor.purple.cgColor
connectedView.inComingLine = line
outGoingLine = line
return line
}
}
class MyViewController : UIViewController {
var dynamicAnimator = UIDynamicAnimator()
var collisionBehavior = UICollisionBehavior()
var gravityBehavior = UIGravityBehavior()
override func loadView() {
let view = UIView()
view.backgroundColor = .white
self.view = view
let viw = MoveAbleView(frame: CGRect(x: 100, y: 200, width: 50, height: 50))
viw.backgroundColor = UIColor.red
self.view.addSubview(viw)
let viw2 = MoveAbleView(frame: CGRect(x: 300, y: 100, width: 50, height: 50))
viw2.backgroundColor = UIColor.orange
self.view.addSubview(viw2)
let gravityViw = MoveAbleView(frame: CGRect(x: 100, y: 0, width: 50, height: 50))
gravityViw.backgroundColor = UIColor.green
self.view.addSubview(gravityViw)
let gravityViw2 = MoveAbleView(frame: CGRect(x: 300, y: -200, width: 50, height: 50))
gravityViw2.backgroundColor = UIColor.blue
self.view.addSubview(gravityViw2)
collisionBehavior.addItem(viw)
collisionBehavior.addItem(viw2)
collisionBehavior.addItem(gravityViw)
collisionBehavior.addItem(gravityViw2)
gravityBehavior.addItem(gravityViw)
gravityBehavior.addItem(gravityViw2)
dynamicAnimator.addBehavior(collisionBehavior)
dynamicAnimator.addBehavior(gravityBehavior)
self.view.layer.addSublayer(viw.lineTo(connectedView: viw2))
}
}
// Present the view controller in the Live View window
PlaygroundPage.current.liveView = MyViewController()
When UIView strikes and moves, how do you redraw a connected line in real time?
Actually, all what you need is another UIView to represent the lines and employ attachmentBehaviors. For instance, there is a line between two attached objects.
class MyViewController : UIViewController {
override func loadView() {
let view = UIView()
view.backgroundColor = .white
self.view = view
dynamicAnimator = UIDynamicAnimator(referenceView: view)
let viw = MoveAbleView(frame: CGRect(x: 100, y: 200, width: 50, height: 50))
viw.backgroundColor = UIColor.red
self.view.addSubview(viw)
let viw2 = MoveAbleView(frame: CGRect(x: 300, y: 200, width: 50, height: 50))
viw2.backgroundColor = UIColor.orange
self.view.addSubview(viw2)
let gravityViw = MoveAbleView(frame: CGRect(x: 100, y: 0, width: 50, height: 50))
gravityViw.backgroundColor = UIColor.green
self.view.addSubview(gravityViw)
let line1 = MoveAbleView(frame: CGRect(x: 125, y: 225, width: 200, height: 10))
line1.backgroundColor = UIColor.purple
self.view.addSubview(line1)
let l1 = UIAttachmentBehavior.init(item: viw, offsetFromCenter: UIOffset.zero, attachedTo: line1, offsetFromCenter: UIOffset.init(horizontal: -100, vertical: 0))
let l2 = UIAttachmentBehavior.init(item: viw2, offsetFromCenter: UIOffset.zero, attachedTo: line1, offsetFromCenter: UIOffset.init(horizontal: 100, vertical: 0))
collisionBehavior.addItem(viw)
collisionBehavior.addItem(viw2)
collisionBehavior.addItem(gravityViw)
gravityBehavior.addItem(gravityViw)
dynamicAnimator.addBehavior(l1)
dynamicAnimator.addBehavior(l2)
dynamicAnimator.addBehavior(collisionBehavior)
dynamicAnimator.addBehavior(gravityBehavior)
}
}

Select audio device output swift (like Waze)

I'm digging all over the web trying to find some clue to achieve this but no success.
I'm trying to show up the user a popup like Waze does to allow the user to select the audio output device, volume etc.
It seems to be some API as it shows exactly as sound widget on iOS11.
Any help appreciated.
For those struggling on it like me, here a potential solution.
I don't know if there is a better solution. If so, please share.
I needed a custom button to make exactly the same MPVolumeView Route Button. So I could achieve the same effect and functionality masking the original button.
import UIKit
import MediaPlayer
class SelectAudioDeviceViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let avView = myView(frame: CGRect(x: 0, y: 0, width: 150, height: 50))
let button = UIButton(frame: CGRect(x: 50, y: 150, width: 150, height: 50))
avView.showsRouteButton = true
avView.showsVolumeSlider = false
avView.setRouteButtonImage(nil, for: .normal)
button.backgroundColor = UIColor.gray
button.setTitle("BLABLA", for: .normal)
button.addSubview(avView)
self.view.addSubview(button)
}
}
class myView: MPVolumeView {
override func routeButtonRect(forBounds bounds: CGRect) -> CGRect {
let newBounds = CGRect(x: 0, y: 0, width: 150, height: 50)
return newBounds
}
}
Hope it helps
Another answer is AVRoutePickerView. See documentation here for more info. That also matches with the screenshot in your question.
SWIFT5
Using AVRoutePickerView for iOS13>
import UIKit
import AVFoundation
import AVKit
let view = UIView(frame: UIScreen.main.bounds)
class ViewController: UIViewController {
var AirPlayButton : AVRoutePickerView!
override func loadView() {
let view = UIView(frame: UIScreen.main.bounds)
view.backgroundColor = .white
AirPlayButton = AVRoutePickerView(frame: CGRect(x: 50, y: 50, width: 100, height: 100))
view.addSubview(AirPlayButton)
self.view = view
let AirPlayButton = AVRoutePickerView(frame: CGRect(x: 50, y: 50, width: 100, height: 100))
view.addSubview(AirPlayButton)
}
}

make everything clear except the fake border in swift

I'm implementing a "fake" border using a view with a background color so that the border doesn't cover another view. (as per this answer and the following code)
UIView *backgroundView = [[UIView alloc] initWithFrame:CGRectMake(100, 100, 200, 200)];
backgroundView.backgroundColor = [UIColor blackColor]; /* I want this to be clear except for the part outside bView */
backgroundView.clipsToBounds = NO;
UIView *bView = [[UIView alloc] initWithFrame:CGRectInset(backgroundView.bounds, 3, 3)];
bView.backgroundColor = [UIColor redColor]; /* I want this to be clear */
UIView *cView = [[UIView alloc] initWithFrame:CGRectMake(-50, -50, 100, 100)];
cView.backgroundColor = [UIColor yellowColor];
[bView addSubview:cView];
[backgroundView addSubview:bView];
[self.window addSubview:backgroundView];
How can I have backgroundView be clear(transparent) except for the border and bview be completely transparent? If I set both color to clear, I will lose my border. I am using swift unlike the code example.
Thanks
This is what I actually want. The big box needs to be transparent except for the fake black border around it so that the text (and everything behind it) can show up.
You say you want this:
So now I will easily construct it, but I will make the third view white so we can see it (with an annotation that it should be clear):
self.view.backgroundColor = .gray
let borderView = UIView(frame:CGRect(x: 150, y: 150, width: 200, height: 200))
borderView.backgroundColor = .clear
borderView.layer.borderWidth = 3
self.view.addSubview(borderView)
let yellowView = UIView(frame:CGRect(x: 100, y: 100, width: 100, height: 100))
yellowView.backgroundColor = .yellow
self.view.addSubview(yellowView)
let clearView = UIView(frame:borderView.frame.insetBy(dx: 3, dy: 3))
clearView.backgroundColor = .white // should be .clear
self.view.addSubview(clearView)
Result:
Substitute .clear for .white to get the desired outcome.
Well, I can't give you definitive performance data, but I would expect this to give you better performance than multiple views to create a "fake" border...
class myView: UIView {
override func draw(_ rect: CGRect) {
UIColor.black.set()
let context = UIGraphicsGetCurrentContext()
context?.stroke(rect.insetBy(dx: 1.5, dy: 1.5), width: 3.0)
}
}
let backgroundView = myView(frame: CGRect(x: 100, y: 100, width: 200, height: 200))
backgroundView.backgroundColor = UIColor.clear
let cView = UIView(frame: CGRect(x: -50, y: -50, width: 100, height: 100))
cView.backgroundColor = UIColor.yellow
backgroundView.addSubview(cView)
self.view.addSubview(backgroundView)
Of course, if you really want to do this with subviews to create the frame, this will also do the job. It adds 4 subviews to the background view, one for each side of the rectangle:
let backgroundView = UIView(frame: CGRect(x: 100, y: 100, width: 200, height: 200))
backgroundView.backgroundColor = UIColor.clear
backgroundView.clipsToBounds = false
let bgvFrame = backgroundView.bounds
let leftEdge = UIView(frame: CGRect(x: 0, y: 0, width: 3, height: bgvFrame.size.height))
leftEdge.backgroundColor = UIColor.black
let topEdge = UIView(frame: CGRect(x: 0, y: 0, width: bgvFrame.size.width, height: 3))
topEdge.backgroundColor = UIColor.black
let rightEdge = UIView(frame: CGRect(x: bgvFrame.size.width - 3, y: 0, width: 3, height: bgvFrame.size.height))
rightEdge.backgroundColor = UIColor.black
let bottomEdge = UIView(frame: CGRect(x: 0, y: bgvFrame.size.height - 3, width: bgvFrame.size.width, height: 3))
bottomEdge.backgroundColor = UIColor.black
backgroundView.addSubview(leftEdge)
backgroundView.addSubview(topEdge)
backgroundView.addSubview(rightEdge)
backgroundView.addSubview(bottomEdge)
let cView = UIView(frame: CGRect(x: -50, y: -50, width: 100, height: 100))
cView.backgroundColor = UIColor.yellow
backgroundView.addSubview(cView)
self.view.addSubview(backgroundView)

AutoLayout for programmatically created uiview element

When i try to create an uiview with loading text and activityindicator, even though it looks centered in iphone 6, it is not centered in iphone 5.
How can i use auto layout for this programatically created UIView ?
I am trying to center this uiview in all screen sizes
boxView = UIView(frame: CGRect(x: TableView.frame.midX - 60, y: TableView.frame.midY - 15, width: 180, height: 50))
boxView.backgroundColor = UIColor.blackColor()
boxView.alpha = 0.5
boxView.layer.cornerRadius = 10
var activityView = UIActivityIndicatorView(activityIndicatorStyle: UIActivityIndicatorViewStyle.WhiteLarge)
activityView.frame = CGRect(x: 0, y: 0, width: 50, height: 50)
activityView.startAnimating()
var textLabel = UILabel(frame: CGRect(x: 60, y: 0, width: 200, height: 50))
textLabel.textColor = UIColor.whiteColor()
textLabel.text = "Loading"
boxView.addSubview(activityView)
boxView.addSubview(textLabel)
TableView.addSubview(boxView)
EDIT: I tried this and it seems working good
var bounds: CGRect = UIScreen.mainScreen().bounds
var width:CGFloat = bounds.size.width
var height:CGFloat = bounds.size.height
boxView = UIView(frame: CGRect(x: width/2 - 90, y: height/2 - 100, width: 180, height: 50))

Resources