I am trying to add alpha to the background view when tapped on a button. So far achieved adding blur but alpha not so much.
How can I add alpha to the background so that when the bottom sheet appears background will be darker and disabled.
let maxDimmedAlpha: CGFloat = 0.2
lazy var dimmedView: UIView = {
let view = UIView()
view.backgroundColor = .black
view.alpha = maxDimmedAlpha
return view
}()
#objc func shareBtnClick() {
dimmedView.frame = self.parentVC.view.bounds
dimmedView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
self.parentVC.view.addSubview(dimmedView)
if self.parentVC.navigationController != nil {
if self.parentVC.navigationController?.viewControllers.count == 1 {
showBottomSheet()
} else {
NotificationCenter.default.post(name: NSNotification.Name("ShowBottomSheet"), object: nil, userInfo: ["itemId": modalSheet(), "delegate": self])
}
} else {
showBottomSheet()
}
}
func showBottomSheet() {
let modalSheet = MainBottomSheet()
modalSheet.data = self.modalSheet()
modalSheet.delegate = self
modalSheet.modalPresentationStyle = .overCurrentContext
self.parentVC.present(modalSheet, animated: true)
}
I was able to produce the dimmed effect using this code in XCode, I'm not sure why it won't work in your project but there is an easy way to debug this.
I suggest using Debug View Hierarchy, one of XCode's best tools in my opinion. This allows you to separate every single layer of the user interface. This way, you can see if your dimmedView is actually being added to the parent view and that its frame is matching the parent view's bounds.
Keep in mind if your background is dark, you won't see this dimmedView because its backgroundColor is set to UIColor.black.
Debug View Hierarchy button
Related
Maybe a bit of a newbie here, so forgive me if the an easy fix, but I have used the below code pre ios13 to display a image on an external display, but now Ive come across a warning.
The warning is 'Setter for 'screen' was deprecated in iOS 13.0'
MY CODE:
#objc func setupScreen(){
if UIScreen.screens.count > 1{
//find the second screen
let secondScreen = UIScreen.screens[1]
secondScreen.overscanCompensation = .none
//set up a window for the screen using the screens pixel dimensions
secondWindow = UIWindow(frame: secondScreen.bounds)
//windows require a root view controller
let viewcontroller = UIViewController()
secondWindow?.rootViewController = viewcontroller
//tell the window which screen to use
secondWindow?.screen = secondScreen
//set the dimensions for the view for the external screen so it fills the screen
secondScreenView = UIView(frame: secondWindow!.frame)
//add the view to the second screens window
secondWindow?.addSubview(secondScreenView!)
//unhide the window
secondWindow?.isHidden = false
//customised the view
let img = UIImage(named: "chart.png")
// view.layer.contents = img?.cgImage
secondScreenView?.layer.contents = img?.cgImage
// secondScreenView!.backgroundColor = UIColor(patternImage: UIImage(named: "chart.png")!)
secondScreenView!.addSubview(externalLabel)
}
}
#objc func screenDisconnected(){
secondWindow = nil
secondScreenView = nil
}
func registerForScreenNotifications(){
NotificationCenter.default.addObserver(self, selector: #selector(testPatterns.setupScreen), name: UIScreen.didConnectNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(testPatterns.screenDisconnected), name: UIScreen.didDisconnectNotification, object: nil)
}
}
I tried looking in the Apple documentation but I can't find anything?
Any help would be great.
P.S it does still work but would like to get rid of the warning.
cheers!
FirebaseUI has a nice pre-buit UI for Swift. I'm trying to position an image view above the login buttons on the bottom. In the example below, the imageView is the "Hackathon" logo. Any logo should be able to show in this, if it's called "logo", since this shows the image as aspectFit.
According to the Firebase docs page:
https://firebase.google.com/docs/auth/ios/firebaseui
You can customize the signin screen with this function:
func authPickerViewController(forAuthUI authUI: FUIAuth) -> FUIAuthPickerViewController {
return FUICustomAuthPickerViewController(nibName: "FUICustomAuthPickerViewController",
bundle: Bundle.main,
authUI: authUI)
}
Using this code & poking around with subviews in the debuggers, I've been able to identify and color code views in the image below. Unfortunately, I don't think that the "true" size of these subview frames is set until the view controller presents, so trying to access the frame size inside these functions won't give me dimensions that I can use for creating a new imageView to hold a log. Plus accessing the views with hard-coded index values like I've done below, seems like a pretty bad idea, esp. given that Google has already changed the Pre-Built UI once, adding a scroll view & breaking the code of anyone who set the pre-built UI's background color.
func authPickerViewController(forAuthUI authUI: FUIAuth) -> FUIAuthPickerViewController {
// Create an instance of the FirebaseAuth login view controller
let loginViewController = FUIAuthPickerViewController(authUI: authUI)
// Set background color to white
loginViewController.view.backgroundColor = UIColor.white
loginViewController.view.subviews[0].backgroundColor = UIColor.blue
loginViewController.view.subviews[0].subviews[0].backgroundColor = UIColor.red
loginViewController.view.subviews[0].subviews[0].tag = 999
return loginViewController
}
I did get this to work by adding a tag (999), then in the completion handler when presenting the loginViewController I hunt down tag 999 and call a function to add an imageView with a logo:
present(loginViewController, animated: true) {
if let foundView = loginViewController.view.viewWithTag(999) {
let height = foundView.frame.height
print("FOUND HEIGHT: \(height)")
self.addLogo(loginViewController: loginViewController, height: height)
}
}
func addLogo(loginViewController: UINavigationController, height: CGFloat) {
let logoFrame = CGRect(x: 0 + logoInsets, y: self.view.safeAreaInsets.top + logoInsets, width: loginViewController.view.frame.width - (logoInsets * 2), height: self.view.frame.height - height - (logoInsets * 2))
// Create the UIImageView using the frame created above & add the "logo" image
let logoImageView = UIImageView(frame: logoFrame)
logoImageView.image = UIImage(named: "logo")
logoImageView.contentMode = .scaleAspectFit // Set imageView to Aspect Fit
// loginViewController.view.addSubview(logoImageView) // Add ImageView to the login controller's main view
loginViewController.view.addSubview(logoImageView)
}
But again, this doesn't seem safe. Is there a "safe" way to deconstruct this UI to identify the size of this button box at the bottom of the view controller (this size will vary if there are multiple login methods supported, such as Facebook, Apple, E-mail)? If I can do that in a way that avoids the hard-coding approach, above, then I think I can reliably use the dimensions of this button box to determine how much space is left in the rest of the view controller when adding an appropriately sized ImageView. Thanks!
John
This should address the issue - allowing a logo to be reliably placed above the prebuilt UI login buttons buttons + avoiding hard-coding the index values or subview locations. It should also allow for properly setting background color (also complicated when Firebase added the scroll view + login button subview).
To use: Create a subclass of FUIAuthDelegate to hold a custom view controller for the prebuilt Firebase UI.
The code will show the logo at full screen behind the buttons if there isn't a scroll view or if the class's private constant fullScreenLogo is set to false.
If both of these conditions aren't meant, the logo will show inset taking into account the class's private logoInsets constant and the safeAreaInsets. The scrollView views are set to clear so that a background image can be set, as well via the private let backgroundColor.
Call it in any signIn function you might have, after setting authUI.providers. Call would be something like this:
let loginViewController = CustomLoginScreen(authUI: authUI!)
let loginNavigationController = UINavigationController(rootViewController: loginViewController)
loginNavigationController.modalPresentationStyle = .fullScreen
present(loginNavigationController, animated: true, completion: nil)
And here's one version of the subclass:
class CustomLoginScreen: FUIAuthPickerViewController {
private var fullScreenLogo = false // false if you want logo just above login buttons
private var viewContainsButton = false
private var buttonViewHeight: CGFloat = 0.0
private let logoInsets: CGFloat = 16
private let backgroundColor = UIColor.white
private var scrollView: UIScrollView?
private var viewContainingButton: UIView?
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// set color of scrollView and Button view inside scrollView to clear in viewWillAppear to avoid a "color flash" when the pre-built login UI first appears
self.view.backgroundColor = UIColor.white
guard let foundScrollView = returnScrollView() else {
print("😡 Couldn't get a scrollView.")
return
}
scrollView = foundScrollView
scrollView!.backgroundColor = UIColor.clear
guard let foundViewContainingButton = returnButtonView() else {
print("😡 No views in the scrollView contain buttons.")
return
}
viewContainingButton = foundViewContainingButton
viewContainingButton!.backgroundColor = UIColor.clear
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// Create the UIImageView at full screen, considering logoInsets + safeAreaInsets
let x = logoInsets
let y = view.safeAreaInsets.top + logoInsets
let width = view.frame.width - (logoInsets * 2)
let height = view.frame.height - (view.safeAreaInsets.top + view.safeAreaInsets.bottom + (logoInsets * 2))
var frame = CGRect(x: x, y: y, width: width, height: height)
let logoImageView = UIImageView(frame: frame)
logoImageView.image = UIImage(named: "logo")
logoImageView.contentMode = .scaleAspectFit // Set imageView to Aspect Fit
logoImageView.alpha = 0.0
// Only proceed with customizing the pre-built UI if you found a scrollView or you don't want a full-screen logo.
guard scrollView != nil && !fullScreenLogo else {
print("No scrollView found.")
UIView.animate(withDuration: 0.25, animations: {logoImageView.alpha = 1.0})
self.view.addSubview(logoImageView)
self.view.sendSubviewToBack(logoImageView) // otherwise logo is on top of buttons
return
}
// update the logoImageView's frame height to subtract the height of the subview containing buttons. This way the buttons won't be on top of the logoImageView
frame = CGRect(x: x, y: y, width: width, height: height - (viewContainingButton?.frame.height ?? 0.0))
logoImageView.frame = frame
self.view.addSubview(logoImageView)
UIView.animate(withDuration: 0.25, animations: {logoImageView.alpha = 1.0})
}
private func returnScrollView() -> UIScrollView? {
var scrollViewToReturn: UIScrollView?
if self.view.subviews.count > 0 {
for subview in self.view.subviews {
if subview is UIScrollView {
scrollViewToReturn = subview as? UIScrollView
}
}
}
return scrollViewToReturn
}
private func returnButtonView() -> UIView? {
var viewContainingButton: UIView?
for view in scrollView!.subviews {
viewHasButton(view)
if viewContainsButton {
viewContainingButton = view
break
}
}
return viewContainingButton
}
private func viewHasButton(_ view: UIView) {
if view is UIButton {
viewContainsButton = true
} else if view.subviews.count > 0 {
view.subviews.forEach({viewHasButton($0)})
}
}
}
Hope this helps any who have been frustrated trying to configure the Firebase pre-built UI in Swift.
I am experiencing an odd behaviour with my UIViewController which contains a UIScrollView.
The ViewController is pushed from another ViewController. The layout for the view is following
UIView
UIScrollView
UIView
UIImageVIew
UILabel
UIStackView
The way I set up my constraints and update the stackView content is the following.
private extension PresentationViewController {
func setupView() {
isScrolling = true
view.addSubview(scrollView)
scrollView.snp.makeConstraints { (builder) in
builder.top.left.bottom.right.equalToSuperview()
}
scrollView.addSubview(contenView)
contenView.snp.makeConstraints { (builder) in
builder.top.left.bottom.right.equalToSuperview()
builder.width.equalToSuperview()
}
contenView.addSubview(headerImageView)
headerImageView.snp.makeConstraints { (builder) in
builder.top.left.right.equalToSuperview()
builder.height.equalTo(view.frame.height / 2)
}
headerImageView.addSubview(headerTitleLabel)
headerTitleLabel.snp.makeConstraints { (builder) in
builder.left.right.equalToSuperview()
builder.centerX.equalToSuperview()
builder.centerY.equalToSuperview()
}
contenView.addSubview(presentationStackView)
presentationStackView.snp.makeConstraints { (builder) in
builder.top.equalTo(headerImageView.snp.bottom).inset(-Margins.xxLarge)
builder.left.right.equalToSuperview()
builder.bottom.equalTo(scrollView.snp.bottom)
}
}
func updateView(presentationResponse: PresentationResponse?) {
guard let presentationResponse = presentationResponse else { return }
let firstPresentation = presentationResponse.presentationItems[0]
titleView.title = presentationResponse.galleryName
headerImageView.sd_setImage(with: URL(string: firstPresentation.imageSet.fullSize ?? ""), placeholderImage: nil)
headerTitleLabel.text = presentationResponse.galleryName
for item in presentationResponse.presentationItems.dropFirst() {
let sectionImageView = UIImageView()
sectionImageView.contentMode = .scaleAspectFit
sectionImageView.clipsToBounds = true
let sectionCaptionLabel = BaseLabel(withConfiguration: .headline)
sectionCaptionLabel.text = item.rowCaption
let sectionView = UIView()
sectionView.addSubview(sectionImageView)
sectionView.addSubview(sectionCaptionLabel)
sectionImageView.sd_setImage(with: URL(string: item.imageSet.defaultImage ?? "")) { [weak self, weak sectionView] (image, error, _, _) in
guard let strongSelf = self,
let sectionView = sectionView,
let image = image else { return }
strongSelf.presentationStackView.addArrangedSubview(sectionView)
sectionImageView.snp.makeConstraints { (builder) in
builder.top.left.bottom.equalToSuperview().inset(Margins.medium)
let width:CGFloat = strongSelf.view.frame.size.width / 3
builder.width.equalTo(width)
builder.height.equalTo(width * (1 / image.aspectRatioValue))
}
sectionCaptionLabel.snp.makeConstraints { (builder) in
builder.left.equalTo(sectionImageView.snp.right).inset(-Margins.medium)
builder.top.right.equalToSuperview().inset(Margins.medium)
}
}
}
}
}
The setupView() is triggered in ViewDidLoad and updateView(:) when an async action to the backend has been completed.
As you might have seen, I am using Snapkit for my constraints.
When inspecting my UI with the debugger tools i get the message:
Scrollable content size is ambiguous for UIScrollView
The issue seems to be related with the UIStackView because it's not showing.
However, if I go back and push again I don't have the error and everything shows correctly.
If the layout displays correctly, you don't really need to worry about the
Scrollable content size is ambiguous for UIScrollView
warning in debug view hierarchy. However, if you want to get rid of it, add a line in your stack view setup:
presentationStackView.snp.makeConstraints { (builder) in
builder.top.equalTo(headerImageView.snp.bottom).inset(-Margins.xxLarge)
builder.left.right.equalToSuperview()
builder.bottom.equalTo(scrollView.snp.bottom)
// add this line
builder.height.equalTo(0).priority(250)
}
That will give the stack view a valid height (of Zero) for auto-layout to consider when it is empty... but the low priority of 250 will allow it to expand vertically as you add arranged subviews.
just add the constraints Equal Height and Equal Width to your StackView View in relation to the Main View and not to your UIScrollView. In other words the Container View's constraints are tied to the UIScrollView's superview.
After that you will not have warnings in your Storyboard and you can continue adding the constraints for your subviews.
I am trying to implement custom activity indicator which is simple rotating UIImageView. I have next code
import Foundation
import UIKit
class CatSpinnerAnimation: UIView {
let catImageView: UIImageView = {
let catImageView = UIImageView()
catImageView.image = UIImage(named: "CatDateIcon-1024")
catImageView.translatesAutoresizingMaskIntoConstraints = false
return catImageView
}()
func startRotating(superView: UIView) {
superView.addSubview(catImageView)
UIApplication.shared.beginIgnoringInteractionEvents()
catImageView.centerXAnchor.constraint(equalTo: superView.centerXAnchor).isActive = true
catImageView.centerYAnchor.constraint(equalTo: superView.centerYAnchor).isActive = true
catImageView.widthAnchor.constraint(equalToConstant: 50).isActive = true
catImageView.heightAnchor.constraint(equalToConstant: 50).isActive = true
let rotate = CGAffineTransform(rotationAngle: CGFloat.pi)
UIView.animate(withDuration: 5.0) {
self.catImageView.transform = rotate
}
}
func stopRotating(superView: UIView) {
UIApplication.shared.endIgnoringInteractionEvents()
catImageView.removeFromSuperview()
}
}
In my ViewControllers I instantiate an instance of this class and call its function
let catSpinnerAnimation = CatSpinnerAnimation()
catSpinnerAnimation.startRotating(superView: view)
But this doesn't show rotating animation at all, just my ImageView in final position (rotated for 189 degrees). I tried many types of animation but still don't have necessary result. I would appreciate any explanation of correct behavior of rotating animations
I think you might need to let the view be added to the view hierarchy first before you can animate it. Try wrapping your call to UIView.animate() in a call to DispatchQueue.main.asyncAfter(deadline: .now() + .01) {}
That would let your view be added to the view hierarchy and get rendered before you submit your animation.
I am doing an app that does background job that can take some time
I want to show a loader in that time
I want a black screen with a simple loader in the front of it
and show it \ hide it,
when I do actions in the background
I want to do a simple half black square with loader circle
that also blocks presses to the screen
Like in this picture:
How can I achieve that and that ?
First create one UIView which you will put in front of your LogIn view. Then add UIActivityIndicatorView to the created UIView.
let loadingIndicatorView = UIView()
let activityIndicatorView = UIActivityIndicatorView(activityIndicatorStyle: .gray)
Now the loadingIndicatorView should have same frame size as your LogIN view. For color you can set your own color with alpha as you want to show LogIn content too. Initially keep it hidden and whenever you want to show it unhide it.
loadingIndicatorView.frame = view.frame
loadingIndicatorView.backgroundColor = .gray
loadingIndicatorView.isHidden = true
Now setup activityIndicatorView, it should be shown at centre,
activityIndicatorView.center = CGPoint(
x: UIScreen.main.bounds.size.width / 2,
y: UIScreen.main.bounds.size.height / 2
)
You can set some color to the indicator,
activityIndicatorView.color = .white
activityIndicatorView.hidesWhenStopped = true
Now add this activityIndicatorView to loadingIndicatorView and loadingIndicatorView to LogIn View.
loadingIndicatorView.addSubview(activityIndicatorView)
view.addSubview(loadingIndicatorView)
Lastly for showing do,
loadingIndicator.startAnimating()
loadingIndicatorView.isHidden = false
And for hiding,
loadingIndicator.stopAnimating()
loadingIndicatorView.isHidden = true
Updated Answer
Since the OP wanted an example code. Hence the updated answer. Hope everyone gets to learn something or the other out of it.
To start with, I created a subclass of UIView and named it PSOverlaySpinner and it looks something like below:
import UIKit
class PSOverlaySpinner: UIView {
//MARK: - Variables
private var isSpinning: Bool = false
private lazy var spinner : UIActivityIndicatorView = {
var spinner = UIActivityIndicatorView(style: UIActivityIndicatorView.Style.white)
spinner.translatesAutoresizingMaskIntoConstraints = false
spinner.hidesWhenStopped = true
return spinner
}()
// MARK: - View Lifecycle Functions
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
init() {
super.init(frame: CGRect.zero)
self.translatesAutoresizingMaskIntoConstraints = false
self.backgroundColor = UIColor.init(white: 0.0, alpha: 0.8)
self.isSpinning = false
self.isHidden = true
createSubviews()
}
deinit {
self.removeFromSuperview()
}
func createSubviews() -> Void {
self.addSubview(spinner)
setupAutoLayout()
}
// MARK: - Private Methods
private func setupAutoLayout() {
if #available(iOS 11.0, *) {
spinner.safeAreaLayoutGuide.centerXAnchor.constraint(equalTo: safeAreaLayoutGuide.centerXAnchor).isActive = true
spinner.safeAreaLayoutGuide.centerYAnchor.constraint(equalTo: safeAreaLayoutGuide.centerYAnchor).isActive = true
} else {
// Fallback on earlier versions
spinner.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
spinner.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
}
}
// MARK: - Public Methods
public func show() -> Void {
DispatchQueue.main.async {
if !self.spinner.isAnimating {
self.spinner.startAnimating()
}
self.isHidden = false
}
isSpinning = true
}
public func hide() -> Void {
DispatchQueue.main.async {
if self.spinner.isAnimating {
self.spinner.stopAnimating()
}
self.isHidden = true
}
isSpinning = false
}
}
Now move onto the ViewController that you want to add this overlay view to. Since I create my views programmatically, I will show how to do it the same way, but you can easily do it via storyboard or xibs.
Step 1 : Initialize
public lazy var spinnerView : PSOverlaySpinner = {
let loadingView : PSOverlaySpinner = PSOverlaySpinner()
return loadingView
}()
Step 2 : Add as a subview
self.view.addSubview(spinnerView)
Step 3 : Set constraints
spinnerView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor).isActive = true
spinnerView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor).isActive = true
spinnerView.topAnchor.constraint(equalTo: self.view.topAnchor).isActive = true
spinnerView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor).isActive = true
Step 4 : To show PSOverlaySpinner
spinnerView.show()
Step 5 : To hide PSOverlaySpinner
spinnerView.hide()
That is it!!
If you want you can go ahead and modify the PSOverlaySpinner as per your needs. For example, you might want to add a UILabel below the spinner indicating him of the type of action taking place and so on.
Before
After
Old Answer
If you wish to do it manually then create a UIView with the its frame matching self.view.bounds, with 0.5-0.7 alpha and black background color. Add UIActivityIndicator as its subview constrained to its center. For a spinner specific to the image you will have to use the open sourced spinners made available. A couple of them can be found here. Once done add this view as the topmost subview in self.view.
You need to import this library SVProgressHUD and then set few properties like as follows:
SVProgressHUD.setDefaultStyle(SVProgressHUDStyle.dark)
SVProgressHUD.setBackgroundColor(.clear)
SVProgressHUD.setForegroundColor(.white)
SVProgressHUD.setDefaultMaskType(.black)
SVProgressHUD.show()
//SVProgressHUD.show(withStatus: "Loading something, Loading something,Loading something ...")
This will produce same UI output as needed by you in OP. You can find a running sample at my repository (TestPreLoader)