I have a method to change the colour of an image. I use this colour icons in menus of my app quickly without having to go any create and image of the correct colour each time.
/// Tint an image with a selected colour.
///
/// - Parameters:
/// - image: The image you wish to colour
/// - imageView: The imageView containing the image
/// - colour: Colour you wish to tint image with. Set as nil if you dont want to change it or im image is multicoloured.
func tint(icon image:UIImage, for imageView:UIImageView, with colour:UIColor?) {
if colour != nil {
let template = image.withRenderingMode(.alwaysTemplate)
imageView.tintColor = colour!
imageView.image = template
} else {
imageView.image = image
}
}
This works fine most of the time. However my problem is trying to set the colour of an image in a navigation bar it doesn't work at all and the image stays its original colour.
I'm trying the following
let dashboardButton = UIButton(type: .custom)
let dashboardButtonImage = UIImage(named: "example image name")
dashboardButton.setImage(dashboardButtonImage.imageResize(sizeChange: CGSize(width: 20, height: 20)), for: .normal)
style.tint(icon: dashboardButtonImage, for: dashboardButton.imageView!, with: .red)
dashboardButton.contentHorizontalAlignment = .fill
dashboardButton.contentVerticalAlignment = .fill
dashboardButton.imageView?.contentMode = .scaleAspectFit
dashboardButton.frame = CGRect(x: 0, y: 0, width: 30, height: 30)
dashboardButton.addTarget(self, action: #selector(ExampleViewController.goToDashboard), for: .touchUpInside)
let dashboardItem = UIBarButtonItem(customView: dashboardButton)
self.navigationItem.setRightBarButtonItems([dashboardItem], animated: false)
I could very easily fix the problem by just making a graphic of the correct colour. However I want to keep these colour changes in app for when a client decides the want to change colours. I'm wondering why I cant get it to work in the nav bar? Is there a fix for this?
There is always a problem accessing a buttons image property of its image view directly like this:
buttom.imageView.image = testImage
You could try this instead which works for me:
func tint(icon image: UIImage, for button: UIButton, with colour: UIColor?) {
if colour != nil {
let template = image.withRenderingMode(.alwaysTemplate)
button.setImage(template, for: .normal)
button.imageView!.tintColor = .red
} else {
button.setImage(image, for: .normal)
}
}
Using that you pass the button instead of the image view and the function uses the setImage method of UIButton to set it. That appears to get round the problem.
Related
I have a UITableViewCell which an UIImageView in it, the image placeholder works very fine but when use SDWebImage to get image from url with an actual data, the image will display outside the UIImageView.
I have tried using below but none worked.
cell.imageView?.contentMode = .scaleAspectFit
cell.imageView?.clipsToBounds = false
cell.imageView?.layer.masksToBounds = true
I have attached reference image to further understanding.
UITableViewCell
I have also tried resizing the image from server to 40 x 40, but still same issue.
//cell.imageView?.sd_setImage(with: URL(string: "\(Constants.string.WEB)/assets/\(dict.image)"), placeholderImage: UIImage(named: Constants.string.IMAGE_PLACEHOLDER_60))
cell.imageView?.sd_setImage(with: URL(string: "\(Constants.string.WEB)/assets/\(dict.image)")) { (image, error, cache, urls) in
if error != nil {
cell.imageView?.image = UIImage(named: Constants.string.IMAGE_PLACEHOLDER_60)!.resizeImageWith(newSize: CGSize(width: 40, height: 40))
} else {
cell.imageView?.image = image!.resizeImageWith(newSize: CGSize(width: 40, height: 40))
}
}
What is the size of the imageView? Did you set any constraints? Also set cell.imageView?.clipsToBounds to true. Ensure that the imageView has height and width constraint set and also leading and top constraints set.
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 created images assets for dark and light mode called myImage. Set the image assets appearance to Any, Dark.
The problem this code gets the same image even if the mode is light or dark.
How can I get the code to select the correct image depending on light or dark mode?
Thanks for your help.
let image = UIImage(named: "image")
let asset = image?.imageAsset
let resolvedImage = asset?.image(with: traitCollection)
If let image = resolvedImage {
myButton.setImage(image, for: .normal)
}
let image = UIImage(named: "image") is usually all that is needed, if you setup your asset correctly. Make sure to have "Appearances" set to "Any, Dark", then add your images in the appropriate slots:
Find out the current used dark/light mode with:
if traitCollection.userInterfaceStyle == .light {
print("Light mode")
} else {
print("Dark mode")
}
Then I'd access the light/dark Images with different name: UIImage:(named: "myImageLightMode") and UIImage:(named: "myImageDarkMode")
Keep in mind that you can tint images like button images in the desired color like the following, when you don't want to create each icon on a different color:
if let originalImage = UIImage(named: "yourButtonImage") {
let tintedImage = originalImage.withRenderingMode(.alwaysTemplate)
customButton.setImage(tintedImage, for: .normal)
customButton.tintColor = UIColor.red
} else {
print("Image not found.")
}
The same can be done on UIImageView's with the extension:
extension UIImageView {
func setImageAndColor(image: UIImage, color: UIColor) {
let templateImage = image.withRenderingMode(.alwaysTemplate)
self.image = templateImage
self.tintColor = color
}
}
access this with:
let imageView = UIImageView()
imageView.setImageAndColor(image: UIImage(named: "yourImage"), color: .red)
What approach would I take to add buttons to an image in xcode? The app is for iPhones and iPads so it needs to be "responsive" somehow. I am trying to do something like this image has sample image map I found a Github project that uses Obj-C, but I am just trying to learn app development and have no idea what I am doing. Eventually, once the button is pressed I will make some kind of popup with an image and a little info. My app is in swift. I would appreciate and tips or direction to tutorials.
Assuming that provided Map is one flat image.
This is sudo and it will explain you the concept.
Works for both iPhone or iPad on different screen sizes.
//make sure you have the correct size of original image.
let originalImageSize: CGSize = CGSize(width: 1000, height: 500)
//Lets create a struct to have the basic information we need
struct Item {
var id: Int
var name: String
var x: Double
var y: Double
}
//Creating some sample button on the map
//x and y are based on the original Image size
let buttonsInfo : [Item] = [
Item(id: 1, name: "France", x: 10, y: 10),
Item(id: 2, name: "Poland", x: 20, y: 30),
Item(id: 3, name: "Location A", x: 50, y: 100),
Item(id: 4, name: "Location B", x: 300, y: 300)
]
//sudo code : plug it with your project real image code
let yourUIImageView = UIImageView(image: UIImage())
//after the image is rendered on UI get the real dimensions
//Size of the Image that is rendered on UI in proper aspect ratio
let renderedImageSize: CGSize = CGSize(width: 500, height: 250)
for item in buttonsInfo {
let button = UIButton()
button.tag = item.id
//
button.frame = CGRect(x: Double(renderedImageSize.width/originalImageSize.width)*item.x,
y: Double(renderedImageSize.height/originalImageSize.height)*item.y,
width: 50, height: 50)
button.backgroundColor = UIColor.red
button.setTitle("Name your Button ", for: .normal)
button.addTarget(self, action: #selector(buttonAction), for: .touchUpInside)
//yourUIImageView is actual view in your code
yourUIImageView.addSubview(button)
}
func buttonAction(sender: UIButton!) {
let item:Item = buttonsInfo.filter{$0.id == sender.tag}
print("Button tapped")
print(item)
}
Note: if you are resizing your yourUIImageView on orientation changes, then you need to do few more changes to move the buttons based on the new aspect ratio. (let us know if you need help)
You can use UITapGestureRecognizer over the image view to perform same as button action
1st Put in viewDidLoad
let tap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(YourViewControllerName.imgAction))
ImageViewName.addGestureRecognizer(tap)
2nd write function to perform action
func imgAction(){
// perform the action here
}
There are two approaches
1. You can add image to a button as shown in the picture
You can place an image in the storyboard and then add a button above it, then remove the text of the button. It's better to add both these things in a UIView so you can make sure that the button is exactly above the image. You can set the constraints for the UIView after that add the image and button and set the constraints of top, left, right and bottom to 0
Note: Make sure that the button is always above the image so it's clickable. Try to color the button in storyboard in order to be certain.
I want to colorize an image from an imageView. After that I want to set this image to a button.
let imageView:UIImageView = UIImageView()
if let myImage = UIImage(named: "ic_star") {
let tintableImage = myImage.imageWithRenderingMode(.AlwaysTemplate)
imageView.image = tintableImage
}
imageView.tintColor = UIColor.redColor()
bestListButton.setImage(imageView.image, forState: .Normal)
This is a snippet, that should work, but it colorize my button-image always in blue.
The debugger say that the color doesn't change. And the simulator change it in blue.
It seems that it doesn't have something to do with my backgroundcolor of image.
EDIT and (hack) solution
Okay, it's quite strange. The blue is because in my interface builder was the tintColor blue.
I changed it to white. Now my star is white.
BUT: When I delete my code, the image switch back to black. So the change of color seems just work in combination with interface builder and code.
EDIT 2
Programmatically solution:
bestListButton.tintColor = UIColor.redColor()
Is your image a PNG file?
Try:
let imageView:UIImageView = UIImageView()
if let myImage = UIImage(named: "ic_star") {
imageView.image = imageView.image!.imageWithRenderingMode(UIImageRenderingMode.AlwaysTemplate)
}
imageView.tintColor = UIColor.redColor()
bestListButton.setImage(imageView.image, forState: .Normal)