I want to change the color of my navigation bar when I scroll up. My scrollViewDidScroll looks like:
override func scrollViewDidScroll(_ scrollView: UIScrollView) {
let safeArea: CGFloat = UIApplication.shared.windows.filter{$0.isKeyWindow}.first?.safeAreaInsets.top ?? 0
let alpha: CGFloat = ((scrollView.contentOffset.y + safeArea) / safeArea)
// This label becomes visible when scrolled up
navTitleLabel.alpha = alpha
self.navigationController?.navigationBar.barTintColor = .yellow.withAlphaComponent(alpha)
}
I even tried hardcoding 0 into .yellow.withAlphaComponent(alpha). But color is still visible. In case you wonder initial value (when not scrolled) of alpha, it is -0.9. How can I make navigation bar slowly visible as user scrolls, like navBarLabel.
Here is youtube link to the behaviour: https://youtu.be/75BjVK-nz4c
You can do this by generating an image from the yellow colour you’re using. Then on scrollView didScroll just set the navigations background image to be the image you generate.
extension UIColor {
func image(_ size: CGSize = CGSize(width: 1, height: 1)) -> UIImage {
return UIGraphicsImageRenderer(size: size).image { rendererContext in
self.setFill()
rendererContext.fill(CGRect(origin: .zero, size: size))
}
}
}
navigationController?.navigationBar.setBackgroundImage(UIColor.orange.withAlphaComponent(alpha).image(),
for: .default)
Related
My UIScrollView bounces back from left side, but stays normal from the right side.
Look at the gif please.
Important note: The buttons should be centered when the screen opens.
To center my buttons, I create them like this:
button.frame = CGRect(
x: screenWidth/2 + gap,
y: 0,
width: buttonWidth,
height: buttonHeight)
buttonsScrollView.addSubview(button)
I already set wide content size
buttonsScrollView.contentSize.width = screenWidth
and adding extra space - screenWidth + 100 does not help, it makes wider only from the right side.
You can do this with "calculated" frames or with auto-layout. In either case, the general idea...
Add your buttons to a UIView - we'll call it buttonsView:
Start the first button at gap distance from Zero, and set the frame of buttonsView to the height of the buttons, and add gap distance to the right-end.
Add buttonsView to the scroll view, at x: 0, y: 0.
Set the scroll view's .contentSize = buttonsView.frame.size.
Do all of that in viewDidLoad().
We can't center the buttonsView until we know the scroll view's frame width, which will likely vary depending on device (and if you rotate the device), so...
Add a class property to track the scroll view's width:
// track the scroll view frame width
var scrollViewWidth: CGFloat = 0
then, in viewDidLayoutSubviews() we know the scroll view's frame, so we'll adjust the .contentOffset.x to horizontally center the buttonsView:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
// here we know frame size, but
// viewDidLayoutSubviews() can (and usually will) be called multiple times
// so we only want to execute this code when the scroll view width changes
if scrollViewWidth != scrollView.frame.width {
scrollViewWidth = scrollView.frame.width
// calculate content offset x so the row of buttons is centered horizontally
scrollView.contentOffset.x = (buttonsView.frame.width - scrollView.frame.width) * 0.5
}
}
Here's a complete sample implementation. No #IBOutlet connections -- everything is done via code -- so just set a view controller's class to CenterScrollViewController:
class CenterScrollViewController: UIViewController {
// create a scroll view
let scrollView: UIScrollView = {
let v = UIScrollView()
// background color so we can see its frame
v.backgroundColor = .systemYellow
return v
}()
// create a buttons holder view
let buttonsView: UIView = {
let v = UIView()
return v
}()
// gap between buttons and on left/right sides of button row
let gap: CGFloat = 12
// buttons will be (round) at 44 x 44 points
let btnSize: CGFloat = 44
// number of buttons
let numButtons: Int = 9
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor(white: 0.9, alpha: 1.0)
title = "Calc"
// add scroll view to view
scrollView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(scrollView)
// add buttons view to scroll view
scrollView.addSubview(buttonsView)
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
// constrain scroll view
// Top + 40
scrollView.topAnchor.constraint(equalTo: g.topAnchor, constant: 40.0),
// Leading and Trailing
scrollView.leadingAnchor.constraint(equalTo: g.leadingAnchor),
scrollView.trailingAnchor.constraint(equalTo: g.trailingAnchor),
// Height equal to button height + "padding" on bottom
scrollView.heightAnchor.constraint(equalToConstant: btnSize + gap),
])
// let's add some buttons to the buttons view
var x: CGFloat = gap
for i in 1...numButtons {
let b = UIButton()
b.backgroundColor = .systemBlue
b.setTitle("\(i)", for: [])
b.setTitleColor(.white, for: .normal)
b.setTitleColor(.lightGray, for: .highlighted)
// let's keep the buttons square (1:1 ratio) so we can make them round
b.frame = CGRect(x: x, y: 0, width: btnSize, height: btnSize)
b.layer.cornerRadius = btnSize * 0.5
b.layer.borderColor = UIColor.black.cgColor
b.layer.borderWidth = 1
buttonsView.addSubview(b)
x += btnSize + gap
}
// x now equals the total buttons width plus gap on each side
// so set the frame size of the buttons view to (x, btnSize)
buttonsView.frame = CGRect(x: 0, y: 0, width: x, height: btnSize)
// set scroll view content size to the size of the buttons view
scrollView.contentSize = buttonsView.frame.size
}
// track the scroll view frame width
var scrollViewWidth: CGFloat = 0
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
// here we know frame size, but
// viewDidLayoutSubviews() can (and usually will) be called multiple times
// so we only want to execute this code when the scroll view width changes
if scrollViewWidth != scrollView.frame.width {
scrollViewWidth = scrollView.frame.width
// calculate content offset x so the row of buttons is centered horizontally
scrollView.contentOffset.x = (buttonsView.frame.width - scrollView.frame.width) * 0.5
}
}
}
Can anyone point me how could I achieve such design in the UITabbar. I have tried adding the back-ground Image, but that does not look like the design. Here the curve is extended beyond the frame of UITabbar, not sure how to add this views on top of active tabbar.
Creating a custom TabBar from UITabBarController can be solved the problem. Instead of adding a direct image to the Tabbar, use an on the fly image using UIGraphicsBeginImageContext for selectedTabBackgroundImage.
Create the image.
Clip the top part round in the image
Here is the example of the code.
import UIKit
class CustomTabBarViewController: UITabBarController {
var topClipSize: CGFloat = 24.5 //Adjust based on the number of tabbar
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let singleTabWidth: CGFloat = self.tabBar.frame.size.width / CGFloat((self.tabBar.items?.count)!)
let singleTabSize = CGSize(width:singleTabWidth , height: self.tabBar.frame.size.height)
// Create the backgound image
let selectedTabBackgroundImage: UIImage = self.imageWithColor(color: .blue, size: singleTabSize)
// Clip the top
self.tabBar.selectionIndicatorImage = selectedTabBackgroundImage.roundTopImage(topClipSize: topClipSize)
}
func imageWithColor(color: UIColor, size: CGSize) -> UIImage {
if size.height > 55 {
topClipSize = 30.0 // iPhone 8 tabbar height is 53 and iPnone X is 83 - We need more space on top.
}
let rect = CGRect(x: 0, y: 0, width: size.width, height: size.height + topClipSize)
UIGraphicsBeginImageContext(rect.size)
let context = UIGraphicsGetCurrentContext()
context!.setFillColor(color.cgColor)
context!.fill(rect)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image!
}
}
extension UIImage {
func roundTopImage(topClipSize: CGFloat) -> UIImage {
let rect = CGRect(origin:CGPoint(x: 0, y: 0), size: self.size)
let rectBounds: CGRect = CGRect(x: rect.origin.x, y: rect.origin.y + (topClipSize * 2), width: rect.size.width, height: rect.size.height - (topClipSize * 2))
let ovalBounds: CGRect = CGRect(x: rect.origin.x - topClipSize, y: rect.origin.y, width: rect.size.width + (topClipSize * 2), height: rect.size.height)
UIGraphicsBeginImageContextWithOptions(self.size, false, 1)
let rectPath = UIBezierPath(rect: rectBounds)
let ovalPath = UIBezierPath(ovalIn: ovalBounds)
rectPath.append(ovalPath)
rectPath.addClip()
self.draw(in: rect)
return UIGraphicsGetImageFromCurrentImageContext()!
}
}
Here is the output:
It's not really possible to elegantly change the native UITabBar's appearance to that extent. Your options are to create a custom container view controller that acts like a UITabBarController, or just hide the default tab bar and implement your own view in that space.
Even though it's less elegant because you'd be just throwing a view on top of the default tab bar, I actually like that method because you retain the benefits of the native UITabBarController (calling self.tabBarController? from its view controllers, it already adjusts layout margins, etc).
To do this, in your subclass of UITabBarController hide the tabBar:
self.tabBar.isHidden = true
self.tabBar.alpha = 0
Then after implementing your custom view however you want, just set the frame of your custom view to self.tabBar.frame in viewDidLayoutSubviews.
For changing viewControllers, call this when the user taps one of your custom tabs:
self.selectedIndex = newIndex
I use an NSTableView to draw some information in NSTableCellView(s). I want to set the background color of the NSTableView to a certain value and the NSTableCellView's background color to another value independently of the alpha component of the colors used. The problem is that if I set a background color with alpha component 0.3 to NSTableCellView, we see the background color of the NSTableView and then the color is not what I set.
I see two options to solve this problem:
draw the background color of the NSTableView without drawing under the rects used by the NSTableCellView(s).
use color theory and CoreGraphics to compute the new color.
I have worked around a bit option 1 and haven't got any result. I am now looking more into option 2.
For example, if I have two colors:
let tableViewBackgroundColor = NSColor(calibratedRed: 48/255, green: 47/255, blue: 46/255, alpha: 1)
let tableViewCellBackgroundColor = NSColor(calibratedRed: 42/255, green: 41/255, blue: 40/255, alpha: 1)
I want, that the resulting color applied to NSTableCellView background:
let targetColor = tableViewCellBackgroundColor.withAplphaComponent(0.3)
even when the color:
let tableViewBackgroundColorWithAlpha = tableViewBackgroundColor.withAlphaComponent(0.3)
is applied to the background of the NSTableView.
I am looking for an extension to NSColor (CGColor would work) like this:
extension NSColor {
///
/// Return the color that needs to be composed with the color parameter
/// in order to result in the current (self) color.
///
func composedColor(with color: NSColor) -> NSColor
}
That could be used like this:
let color = targetColor.composedColor(with:
tableViewBackgroundColorWithAlpha)
Any idea?
Answering my own question, the solution for this problem has finally been to avoid drawing the parts with table view cells (option 1 on the question). I inspired myself from Drawing Custom Alternating Row Backgrounds in NSTableViews with Swift to implement the final solution.
It basically involve overriding the method func drawBackground(inClipRect clipRect: NSRect) of the NSTableView but avoiding drawing the background part of the table where there is cells present.
Here is the solution:
override func drawBackground(inClipRect clipRect: NSRect) {
super.drawBackground(inClipRect: clipRect)
drawTopBackground(inClipRect: clipRect)
drawBottomBackground(inClipRect: clipRect)
}
private func drawTopBackground(inClipRect clipRect: NSRect) {
guard clipRect.origin.y < 0 else { return }
let rectHeight = rowHeight + intercellSpacing.height
let minY = NSMinY(clipRect)
var row = 0
currentBackgroundColor.setFill()
while true {
let rowRect = NSRect(x: 0, y: (rectHeight * CGFloat(row) - rectHeight), width: NSMaxX(clipRect), height: rectHeight)
if self.rows(in: rowRect).isEmpty {
rowRect.fill()
}
if rowRect.origin.y < minY { break }
row -= 1
}
}
private func drawBottomBackground(inClipRect clipRect: NSRect) {
let rectHeight = rowHeight + intercellSpacing.height
let maxY = NSMaxY(clipRect)
var row = rows(in: clipRect).location
currentBackgroundColor.setFill()
while true {
let rowRect = NSRect(
x: 0,
y: (rectHeight * CGFloat(row)),
width: NSMaxX(clipRect),
height: rectHeight)
if self.rows(in: rowRect).isEmpty {
rowRect.fill()
}
if rowRect.origin.y > maxY { break }
row += 1
}
}
I've been trying to make a zoom animation like Facebook when you click a picture into a cell to move into the middle of the screen. The animation works, but for a reason that I can not figure out, it is not starting from the initial position it is giving me another frame. Please help, I've been struggling with this for a few days now.
I am using a collectionView with CustomCell and everything it's done programmatically:
The function in CenterVC:
//MARK: Function to animate Image View (it will animate to the middle of the View)
func animateImageView(statusImageView : UIImageView) {
//Get access to a starting frame
statusImageView.frame.origin.x = 0
if let startingFrame = statusImageView.superview?.convert(statusImageView.frame, to: nil) {
//Add the view from cell to the main view
let zoomImageView = UIView()
zoomImageView.backgroundColor = .red
zoomImageView.frame = statusImageView.frame
view.addSubview(zoomImageView)
print("Starting frame is: \(startingFrame)")
print("Image view frame is: \(statusImageView.frame)")
UIView.animate(withDuration: 0.75, animations: {
let height = (self.view.frame.width / startingFrame.width) * startingFrame.height
let y = self.view.frame.height / 2 - (height / 2)
zoomImageView.frame = CGRect(x: 0, y: y, width: self.view.frame.width, height: height)
})
}
}
This is the pictureView inside the cell and the constraints (this is where I am setting up the picture for the view, and I am using in cellForRowAtIndexPath cell.centerVC = self):
var centerVC : CenterVC?
func animate() {
centerVC?.animateImageView(statusImageView: pictureView)
}
let pictureView : UIImageView = {
let imageView = UIImageView()
imageView.contentMode = .scaleAspectFill
imageView.image = UIImage(named: "cat")
imageView.backgroundColor = UIColor.clear
imageView.layer.masksToBounds = true
imageView.isUserInteractionEnabled = true
imageView.translatesAutoresizingMaskIntoConstraints = false
return imageView
}()
addConstraintsWithFormat(format: "V:|-5-[v0(40)]-5-[v1]-5-[v2(200)]", views: profileImage, postTextView, pictureView)
addConstraintsWithFormat(format: "H:|[v0]|", views: pictureView)
This is what it prints out in the debugger:
Starting frame is: (5.0, 547.5, 365.0, 200.0)
Image view frame is: (0.0, 195.5, 365.0, 200.0)
As you can see the starting frame it's different from the initial frame and position of the picture. The animation it's not leaving the initial position it just appears somewhere on top and animates to the middle. I don't know what to do, please advice.
For some reason, it was not reading the starting frame rect so I've made a new CGRect that gets the origin and set the size of the picture: (it animates perfectly now)
zoomImageView.frame = CGRect(origin: startingFrame.origin, size: CGSize(width: view.frame.width, height: statusImageView.frame.height))
The Problem
I have a UISearchController embedded within a UIView. The searchbar takes up the entire size of the view. However, there's this grey border that I can't figure how to get rid off. I want the search bar to take up the entire size of the view without that pesky grey border. How can I get rid of it?
I've tried
searchController?.searchBar.backgroundImage = UIImage()
But all that does is make the grey border clear (not to mention it makes the searchResultsController of the searchController not show up):
I want the border to be gone and the search bar take up the entire frame of the UIView.
let SEARCH_BAR_SEARCH_FIELD_KEY = "searchField"
let SEARCH_BAR_PLACEHOLDER_TEXT_KEY = "_placeholderLabel.textColor"
func customiseSearchBar() {
searchBar.isTranslucent = true
searchBar.backgroundImage = UIImage.imageWithColor(color: UIColor.yellow, size: CGSize(width: searchBar.frame.width,height: searchBar.frame.height))
//modify textfield font and color attributes
let textFieldSearchBar = searchBar.value(forKey: SEARCH_BAR_SEARCH_FIELD_KEY) as? UITextField
textFieldSearchBar?.backgroundColor = UIColor.yellow
textFieldSearchBar?.font = UIFont(name: "Helvetica Neue" as String, size: 18)
textFieldSearchBar?.setValue(UIColor.yellow, forKeyPath: SEARCH_BAR_PLACEHOLDER_TEXT_KEY)
}
extension UIImage {
class func imageWithColor(color: UIColor, size: CGSize) -> UIImage {
let rect: CGRect = CGRect(x: 0, y: 0, width: size.width, height: size.height)
UIGraphicsBeginImageContextWithOptions(size, false, 0)
color.setFill()
UIRectFill(rect)
let image: UIImage = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
return image
}
}
This achieves the UI as per your requirement, let me know if you need more customization on this. This is in swift 3 by the way.