Add a blur effect to a UIButton - swift

How can i make a button background get blurred?
Ill found out to get a blurred effect with this:
let blur = UIVisualEffectView(effect: UIBlurEffect(style: UIBlurEffectStyle.Light))
blur.frame = buttonForDisabling.frame
self.tableView.addSubview(blur)
This is working, but only makes a blur with the same frame as my button. I would like to have a button with a blurred background. Is that possible in iOS8?

If you are using a button with an image, bring the imageview to front:
buttonForDisabling.bringSubview(toFront: buttonForDisabling.imageView!)
or just use this extension:
import Foundation
import UIKit
extension UIButton
{
func addBlurEffect()
{
let blur = UIVisualEffectView(effect: UIBlurEffect(style: .light))
blur.frame = self.bounds
blur.isUserInteractionEnabled = false
self.insertSubview(blur, at: 0)
if let imageView = self.imageView{
self.bringSubview(toFront: imageView)
}
}
}

This can be done by creating the blur as you did and inserting it below the titleLabel of the UIButton. It's important to disable user interaction in order to allow the touches to not be intercepted by the the visual effect view and forwarded to the button.
Swift 4.2:
let blur = UIVisualEffectView(effect: UIBlurEffect(style:
UIBlurEffect.Style.light))
blur.frame = buttonForDisabling.bounds
blur.isUserInteractionEnabled = false //This allows touches to forward to the button.
buttonForDisabling.insertSubview(blur, at: 0)

Used Sherwin's great answer, but it didn't quite work for me as I use auto layout and my frame was .zero while this code runs. So I modified the code to use auto-layout and this now works for me:
import Foundation
import UIKit
extension UIButton {
func addBlurEffect(style: UIBlurEffect.Style = .regular, cornerRadius: CGFloat = 0, padding: CGFloat = 0) {
backgroundColor = .clear
let blurView = UIVisualEffectView(effect: UIBlurEffect(style: style))
blurView.isUserInteractionEnabled = false
blurView.backgroundColor = .clear
if cornerRadius > 0 {
blurView.layer.cornerRadius = cornerRadius
blurView.layer.masksToBounds = true
}
self.insertSubview(blurView, at: 0)
blurView.translatesAutoresizingMaskIntoConstraints = false
self.leadingAnchor.constraint(equalTo: blurView.leadingAnchor, constant: padding).isActive = true
self.trailingAnchor.constraint(equalTo: blurView.trailingAnchor, constant: -padding).isActive = true
self.topAnchor.constraint(equalTo: blurView.topAnchor, constant: padding).isActive = true
self.bottomAnchor.constraint(equalTo: blurView.bottomAnchor, constant: -padding).isActive = true
if let imageView = self.imageView {
imageView.backgroundColor = .clear
self.bringSubviewToFront(imageView)
}
}
}

Swift 5 version of #tuvok's excellent response. I've also updated it and added options to control various aspects such as:
• style: The blur style
• cornerRadius: Allows you to make it rounded or a circle
• padding: This is useful if you want to use a cornerRadius but you want to make the size of the button larger for hit-testing.
extension UIButton {
func addBlurEffect(style: UIBlurEffect.Style = .regular, cornerRadius: CGFloat = 0, padding: CGFloat = 0) {
backgroundColor = .clear
let blurView = UIVisualEffectView(effect: UIBlurEffect(style: style))
blurView.frame = bounds.inset(by: UIEdgeInsets(horizontal: padding, vertical: padding))
blurView.isUserInteractionEnabled = false
blurView.backgroundColor = .clear
if cornerRadius > 0 {
blurView.layer.cornerRadius = cornerRadius
blurView.layer.masksToBounds = true
}
self.insertSubview(blurView, at: 0)
if let imageView = self.imageView{
imageView.backgroundColor = .clear
self.bringSubviewToFront(imageView)
}
}
}

Related

UIKit - How to prevent default blur and focus background of UITextField?

As you can see in the image, the UITextField (only on tvOS?) by default has these behaviors:
A translucent overlay (make the background and the white text grey in the second button)
A white background and bigger size when it's focused (the first button)
How do I remove/change these behaviors?
What did I do?
I tried to change all color related property (except text color) in Interface Builder to Clear color
I used these code to build the view programmatically
let view = UITextField()
view.backgroundColor = .clear
view.translatesAutoresizingMaskIntoConstraints = false
view.font = view.font?.withSize(16)
view.textAlignment = .center
view.borderStyle = .none
view.tintColor = .clear
view.tintAdjustmentMode = .normal
view.accessibilityIgnoresInvertColors = true
view.textColor = .white
view.disabledBackground = .none
view.background = .none
view.layer.backgroundColor = CGColor(gray: 0.0, alpha: 0.0)
view.layer.shadowColor = CGColor(gray: 0.0, alpha: 0.0)
view.layer.borderColor = CGColor(gray: 0.0, alpha: 0.0)
view.layer.shadowOpacity = 0.0
Additional Information
A new tvOS app project, created with XCode 12.2 in macOS 10.15.7
Run on Apple TV simulator
No additional libraries/pods used
Actually we always can inherit UIKit classes and do whatever layout/style we want. Here is very raw demo of how this can be done.
Prepared & tested with Xcode 12.1 / tvOS 14.0
So just substitute custom subclass of UITextField class
class MyTextField: UITextField {
lazy var textLayer = CATextLayer()
override func didMoveToSuperview() {
super.didMoveToSuperview()
layer.backgroundColor = UIColor.clear.cgColor
textLayer.font = self.font
textLayer.fontSize = 36
textLayer.foregroundColor = UIColor.white.cgColor
textLayer.alignmentMode = .center
textLayer.frame = layer.bounds
layer.addSublayer(textLayer)
layer.borderWidth = 2
}
override func layoutSublayers(of layer: CALayer) {
layer.borderColor = self.isFocused ? UIColor.black.cgColor : UIColor.clear.cgColor
textLayer.frame = layer.bounds
textLayer.string = self.text?.isEmpty ?? true ? self.placeholder : self.text
}
override func addSubview(_ view: UIView) {
// blocks standard styling
}
}

UIScrollView with Embedded UIImageView; how to get the image to fill the screen

UIKit/Programmatic UI
I have an UIScrollView with an UIImageView inside. The image is set by user selection and can have all kinds of sizes. What I want is that the image initially fills the screen (view) and then can be zoomed and scrolled all the way to the edges of the image.
If I add the ImageView directly to the view (no scrollView), I get it to fill the screen with the following code:
mapImageView.image = ProjectImages.projectDefaultImage
mapImageView.translatesAutoresizingMaskIntoConstraints = false
mapImageView.contentMode = .scaleAspectFill
view.addSubview(mapImageView)
Now the same with the scrollView and the embedded imageView:
view.insertSubview(mapImageScrollView, at: 0)
mapImageScrollView.delegate = self
mapImageScrollView.translatesAutoresizingMaskIntoConstraints = false
mapImageScrollView.contentMode = .scaleAspectFill
mapImageScrollView.maximumZoomScale = 4.0
mapImageScrollView.pinToEdges(of: view, safeArea: true)
mapImageView.image = ProjectImages.projectDefaultImage
mapImageView.translatesAutoresizingMaskIntoConstraints = false
mapImageView.contentMode = .scaleAspectFill
mapImageScrollView.addSubview(mapImageView)
And now, if the image's height is smaller than the view's height, the image does not fill the screen and I'm left with a blank view area below the image. I can zoom and scroll ok, and then the image does fill the view.
Adding contsraints will fill the view as I want, but interferes with the zooming and scrolling and prevents me getting to the edges of the image when zoomed in.
How to set this up correctly ?
You might find this useful...
It allows you to zoom an image in a scrollView, starting with it centered and maintaining aspect ratio.
Here's a complete implementation. It has two important variables at the top:
// can be .scaleAspectFill or .scaleAspectFit
var fitMode: UIView.ContentMode = .scaleAspectFill
// if fitMode is .scaleAspectFit, allowFullImage is ignored
// if fitMode is .scaleAspectFill, image will start zoomed to .scaleAspectFill
// if allowFullImage is false, image will zoom back to .scaleAspectFill if "pinched in"
// if allowFullImage is true, image can be "pinched in" to see the full image
var allowFullImage: Bool = true
Everything is done via code - no #IBOutlet or other connections - so just create add a new view controller and assign its custom class to ZoomAspectViewController (and edit the name of the image you want to use):
class ZoomAspectViewController: UIViewController, UIScrollViewDelegate {
var scrollView: UIScrollView!
var imageView: UIImageView!
var imageViewBottomConstraint: NSLayoutConstraint!
var imageViewLeadingConstraint: NSLayoutConstraint!
var imageViewTopConstraint: NSLayoutConstraint!
var imageViewTrailingConstraint: NSLayoutConstraint!
// can be .scaleAspectFill or .scaleAspectFit
var fitMode: UIView.ContentMode = .scaleAspectFit
// if fitMode is .scaleAspectFit, allowFullImage is ignored
// if fitMode is .scaleAspectFill, image will start zoomed to .scaleAspectFill
// if allowFullImage is false, image will zoom back to .scaleAspectFill if "pinched in"
// if allowFullImage is true, image can be "pinched in" to see the full image
var allowFullImage: Bool = true
override func viewDidLoad() {
super.viewDidLoad()
guard let img = UIImage(named: "myImage") else {
fatalError("Could not load the image!!!")
}
scrollView = UIScrollView()
imageView = UIImageView()
scrollView.translatesAutoresizingMaskIntoConstraints = false
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.contentMode = .scaleToFill
scrollView.addSubview(imageView)
view.addSubview(scrollView)
// respect safe area
let g = view.safeAreaLayoutGuide
imageViewTopConstraint = imageView.topAnchor.constraint(equalTo: scrollView.topAnchor)
imageViewBottomConstraint = imageView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor)
imageViewLeadingConstraint = imageView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor)
imageViewTrailingConstraint = imageView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor)
NSLayoutConstraint.activate([
scrollView.topAnchor.constraint(equalTo: g.topAnchor),
scrollView.bottomAnchor.constraint(equalTo: g.bottomAnchor),
scrollView.leadingAnchor.constraint(equalTo: g.leadingAnchor),
scrollView.trailingAnchor.constraint(equalTo: g.trailingAnchor),
imageViewTopConstraint,
imageViewBottomConstraint,
imageViewLeadingConstraint,
imageViewTrailingConstraint,
])
scrollView.delegate = self
scrollView.minimumZoomScale = 0.1
scrollView.maximumZoomScale = 5.0
imageView.image = img
imageView.frame.size = img.size
}
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
coordinator.animate(alongsideTransition: { _ in
self.updateMinZoomScaleForSize(size, shouldSize: (self.scrollView.zoomScale == self.scrollView.minimumZoomScale))
self.updateConstraintsForSize(size)
}, completion: {
_ in
})
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
updateMinZoomScaleForSize(scrollView.bounds.size)
updateConstraintsForSize(scrollView.bounds.size)
if fitMode == .scaleAspectFill {
centerImageView()
}
}
func updateMinZoomScaleForSize(_ size: CGSize, shouldSize: Bool = true) {
guard let img = imageView.image else {
return
}
var bShouldSize = shouldSize
let widthScale = size.width / img.size.width
let heightScale = size.height / img.size.height
var minScale = min(widthScale, heightScale)
let startScale = max(widthScale, heightScale)
if fitMode == .scaleAspectFill && !allowFullImage {
minScale = startScale
}
if scrollView.zoomScale < minScale {
bShouldSize = true
}
scrollView.minimumZoomScale = minScale
if bShouldSize {
scrollView.zoomScale = fitMode == .scaleAspectFill ? startScale : minScale
}
}
func scrollViewDidZoom(_ scrollView: UIScrollView) {
updateConstraintsForSize(scrollView.bounds.size)
}
func centerImageView() -> Void {
let yOffset = (scrollView.frame.size.height - imageView.frame.size.height) / 2
let xOffset = (scrollView.frame.size.width - imageView.frame.size.width) / 2
scrollView.contentOffset = CGPoint(x: -xOffset, y: -yOffset)
}
func updateConstraintsForSize(_ size: CGSize) {
let yOffset = max(0, (size.height - imageView.frame.height) / 2)
imageViewTopConstraint.constant = yOffset
imageViewBottomConstraint.constant = yOffset
let xOffset = max(0, (size.width - imageView.frame.width) / 2)
imageViewLeadingConstraint.constant = xOffset
imageViewTrailingConstraint.constant = xOffset
view.layoutIfNeeded()
}
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return imageView
}
}
Edit
As an example, I used this image (2560 x 1440):
and I get this result on launch:
and maximum zoom in (5.0) scrolled to top-center:
Edit 2
Same image, at launch, with:
var fitMode: UIView.ContentMode = .scaleAspectFill
instead of .scaleAspectFit:
I've found this solution that works for me, when setting and changing the image, I calculate the minimum needed zoom scale and set it on the scrollView:
var selectedMapImage: MapImage? {
didSet {
mapImageView.image = mapImagesController.getImageForMapImage(selectedMapImage!)
mapImageScrollView.minimumZoomScale = view.bounds.height / mapImageView.image!.size.height
mapImageScrollView.setZoomScale(mapImageScrollView.minimumZoomScale, animated: true)
mapImageScrollView.scrollRectToVisible(view.bounds, animated: true)
}
}

How do I make a UICollectionView transparent?

I have tried virtually (literally?) everything to make a horizontal collection view transparent which is overlaid on a map view.
This is my collection view:
fileprivate let restaurantCollection : UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
layout.minimumInteritemSpacing = 0
layout.minimumLineSpacing = 0
let restaurant = UICollectionView(frame: .zero, collectionViewLayout: layout)
restaurant.decelerationRate = .fast
restaurant.backgroundView = nil
restaurant.layer.backgroundColor = UIColor.clear.cgColor
restaurant.backgroundColor = .clear
restaurant.translatesAutoresizingMaskIntoConstraints = false
restaurant.register(MapCell.self, forCellWithReuseIdentifier: "Cell")
return restaurant
}()
As you can see it has a clear background, clear layer and the background view is nil.
And the cell is transparent as well:
func setupView() {
contentView.backgroundColor = UIColor.clear.withAlphaComponent(0)
contentView.addSubview(cellBackground)
cellBackground.translatesAutoresizingMaskIntoConstraints = false
cellBackground.topAnchor.constraint(equalTo: contentView.topAnchor).isActive = true
cellBackground.leadingAnchor.constraint(equalTo: contentView.leadingAnchor).isActive = true
cellBackground.trailingAnchor.constraint(equalTo: contentView.trailingAnchor).isActive = true
cellBackground.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).isActive = true
}
I also added a view to the cell "backgroundView" that's pinned with constant 0 to all sides. And this too is clear.
lazy var cellBackground : UIView = {
let view = UIView(frame: CGRect(x: 10, y: 6, width: self.frame.width, height: 220))
view.clipsToBounds = true
view.backgroundColor = .clear
view.layer.backgroundColor = UIColor.clear.cgColor
return view
}()
And after all that effort, the collection view still has a white background
View hierarchy: Here is the view that is still white
You need to make sure that you are setting backgroundColor to clear
self.collectionView.backgroundColor = UIColor.clear
self.collectionView.backgroundView = UIView.init(frame: CGRect.zero)
For Modern List Layout CollectionViews:
UICollectionLayoutListConfiguration.backgroundColor = .clear
UICollectionViewListCell.backgroundConfiguration = .clear()

Customize UISlider Height - Programmatically Created

How do you create a custom height for a Slider when the slider has been created programmatically?
The answers to this similar question explain how to do it when the Slide is setup with a Storyboard. What if you aren't using a Storyboard and the slider is set up along these lines programmatically? Thanks!
let slider: UISlider = {
let slider = UISlider()
slider.minimumValue = 1
slider.maximumValue = 7
slider.value = 1
slider.maximumTrackTintColor = UIColor.red
slider.minimumTrackTintColor = UIColor.green
slider.thumbTintColor = UIColor.blue
slider.isContinuous = true
slider.translatesAutoresizingMaskIntoConstraints = false
return slider
}()
You'll need to create a custom slider, and override trackRect(forBounds bounds: CGRect) -> CGRect
class CustomSlider: UISlider {
let trackHeight: CGFloat = 12 //desired track width, in points
override func trackRect(forBounds bounds: CGRect) -> CGRect {
let track = super.trackRect(forBounds: bounds)
return CGRect(x: track.origin.x, y: track.origin.y, width: track.width, height: trackHeight)
}
}
and change your implementation to use this
let slider: CustomSlider = {
let slider = CustomSlider()
slider.minimumValue = 1
slider.maximumValue = 7
slider.value = 1
slider.maximumTrackTintColor = UIColor.red
slider.minimumTrackTintColor = UIColor.green
slider.thumbTintColor = UIColor.blue
slider.isContinuous = true
slider.translatesAutoresizingMaskIntoConstraints = false
return slider
}()

textview shakes when resizing view

I'm resizing the view that a textview belongs to and the text shakes when the view either gets bigger or gets smaller.
Declaration of said text view:
lazy var textview: UITextView = {
let textView = UITextView()
textView.text = ""
textView.font = .systemFont(ofSize: 12, weight: UIFontWeightMedium)
textView.isScrollEnabled = false
textView.isEditable = false
textView.isSelectable = true
textView.isUserInteractionEnabled = true
textView.translatesAutoresizingMaskIntoConstraints = false
textView.textAlignment = .center
textView.textColor = .lightGray
textView.dataDetectorTypes = .link
return textView
}()
I'm resizing the view that it's in to fit the full screen like this
if let window = UIApplication.shared.keyWindow {
let statusBarHeight = UIApplication.shared.statusBarFrame.size.height
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveLinear, animations: {
self.frame = CGRect(x: 0, y: statusBarHeight, width: window.frame.width, height: window.frame.height - statusBarHeight)
self.layer.cornerRadius = 0
self.layoutIfNeeded()
}, completion: nil)
}
Upon doing so, the view expands perfectly but the textviews text does a bounce effect that makes the animation look extremely unprofessional... any advice?
Edit: It seems like when I remove the center text alignment option it works fine. How do I make it work with the text center aligned?
edit: I took another look at this and attempted to use the technique based in UIScrollView animation of height and contentOffset "jumps" content from bottom.
Here's a minimal working example with text view with centered text alignment which is working for me!
I'd recommend managing animations either to be all constraint based, or all frame based. I attempted a version where the animation is driven by updating the container view frame but it was starting to take too long to left it at this constraint based approach.
Hope this points you in the right direction :)
import UIKit
class ViewController: UIViewController {
lazy var textView: UITextView = {
let textView = UITextView()
textView.text = "testing text view"
textView.textAlignment = .center
textView.translatesAutoresizingMaskIntoConstraints = false
return textView
}()
lazy var containerView: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
var widthConstraint: NSLayoutConstraint!
var topAnchor: NSLayoutConstraint!
override func viewDidLoad() {
view.backgroundColor = .groupTableViewBackground
// add container view and constraints
view.addSubview(containerView)
containerView.frame = view.bounds.insetBy(dx: 100, dy: 200)
containerView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
containerView.heightAnchor.constraint(equalToConstant: 100).isActive = true
// keep reference to topAnchor and width as properties to animate
topAnchor = containerView.topAnchor.constraint(lessThanOrEqualTo: view.topAnchor, constant: 100)
widthConstraint = containerView.widthAnchor.constraint(equalToConstant: 300)
topAnchor.isActive = true
widthConstraint.isActive = true
// add text view to container view and set constraints
containerView.addSubview(textView)
textView.leftAnchor.constraint(equalTo: containerView.leftAnchor).isActive = true
textView.rightAnchor.constraint(equalTo: containerView.rightAnchor).isActive = true
textView.topAnchor.constraint(equalTo: containerView.topAnchor).isActive = true
textView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor).isActive = true
}
#IBAction func toggleResize(_ sender: UIButton) {
sender.isSelected = !sender.isSelected
view.layoutIfNeeded()
widthConstraint.constant = sender.isSelected ? view.bounds.width : 300
topAnchor.constant = sender.isSelected ? 20 : 100
// caculate the textView content offset for starting position based on
// expected end position at end of the animation
let xOffset = (textView.bounds.width - widthConstraint.constant) / 2
textView.contentOffset = CGPoint(x: -xOffset, y: textView.contentOffset.y)
UIView.animate(withDuration: 1) {
self.view.layoutIfNeeded()
}
}
}