I would like to blend a UIView with my app's background, using a special blend mode (in my case, the Overlay mode). However, the view to blend is contained in a complex hierarchy of views.
Blending a view with its direct siblings can be achieved using view.layer.compositingFilter = "overlayBlendMode", but the view won't blend with non-siblings views, like the app background.
To recreate the problem, I made the following playground:
import UIKit
import PlaygroundSupport
class MyViewController : UIViewController {
override func loadView() {
let parentView = UIView()
parentView.backgroundColor = .purple
// Child view
let childView = UIView(frame: CGRect(x: 50, y: 50, width: 200, height: 200))
childView.layer.borderColor = UIColor.orange.cgColor
childView.layer.borderWidth = 3
parentView.addSubview(childView)
// Child child view
let childChildView = UIView(frame: CGRect(x: 50, y: 50, width: 100, height: 50))
childChildView.backgroundColor = .white
childChildView.layer.compositingFilter = "overlayBlendMode"
childView.addSubview(childChildView)
self.view = parentView
}
}
// Present the view controller in the Live View window
PlaygroundPage.current.liveView = MyViewController()
We can see here that the child child view, in white, is not blended:
Whereas the view should appear blended like this (the border should not change color):
To create the second picture, I applied the compositing filter on the childView instead of the childChildView, which will blend all the other subviews — therefore it's not what I want. I just want this specific view to be blended.
Note: this view is supposed to move, because it's inside a UIScrollView.
EDIT: More complex example with image background and scrollviews
import UIKit
import PlaygroundSupport
class MyViewController : UIViewController {
override func loadView() {
let parentView = UIView()
// Background image
let backgroundImageView = UIImageView(image: UIImage(named: "image.jpg")!)
backgroundImageView.frame = UIScreen.main.bounds
parentView.addSubview(backgroundImageView)
// Page view (horizontal scrollview)
let pageView = UIScrollView(frame: CGRect(x: 50, y: 50, width: 200, height: 200))
pageView.contentSize = CGSize(width: 600, height: 200)
pageView.flashScrollIndicators()
pageView.layer.borderColor = UIColor.orange.cgColor
pageView.layer.borderWidth = 3
parentView.addSubview(pageView)
// Child view (vertical scrollview)
let childView = UIScrollView(frame: CGRect(x: 20, y: 20, width: 100, height: 150))
childView.contentSize = CGSize(width: 100, height: 300)
childView.layer.borderColor = UIColor.green.cgColor
childView.layer.borderWidth = 3
pageView.addSubview(childView)
// Child child view
let childChildView = UIView(frame: CGRect(x: 50, y: 50, width: 100, height: 50))
childChildView.backgroundColor = .white
childChildView.layer.compositingFilter = "overlayBlendMode"
childView.addSubview(childChildView)
self.view = parentView
}
}
// Present the view controller in the Live View window
PlaygroundPage.current.liveView = MyViewController()
UPDATE 2:
I've tried several ways including adding layers or creating custom image filters that use the background image as input image but none of these solutions got the desired result. The main problem was always the view hierarchy.
I may have found a solution by using a generated image of the views or the actual background image as the content background of the childView once the childChildView is being created but before being displayed. I've changed your example code a bit to add a scroll view and background image in the parentView. See if this works for you / is your desired result:
import UIKit
import PlaygroundSupport
class MyViewController : UIViewController {
override func loadView() {
let parentView = UIView()
parentView.backgroundColor = .purple
let imageName = "image.jpg"
let image = UIImage(named: imageName)
let imageWidth = Int((image?.size.width)!)
let imageheight = Int((image?.size.height)!)
let imageView = UIImageView(image: image!)
imageView.frame = CGRect(x: 50, y: 50, width: imageWidth , height: imageheight)
parentView.addSubview(imageView)
// Child view as UIScrollView
let childView = UIScrollView(frame: CGRect(x: 55, y: 55, width: imageWidth - 10, height: imageheight - 10 ))
childView.contentSize = CGSize(width: imageWidth - 10, height: 5000)
childView.layer.borderColor = UIColor.orange.cgColor
childView.flashScrollIndicators()
childView.layer.borderWidth = 10
parentView.addSubview(childView)
// ChildChild view
let childChildView = UIView(frame: CGRect(x: 15, y: 100, width: 85, height: imageheight - 180))
childChildView.layer.compositingFilter = "overlayBlendMode"
childChildView.backgroundColor = .white
//Creating a static image of the background views BEFORE adding the childChildView.
let format = UIGraphicsImageRendererFormat()
format.scale = 1
format.preferredRange = .standard ///color profile
///Change the imageView to the parentView size of the app. Not available if not set in the playground.
let renderer = UIGraphicsImageRenderer(size: imageView.bounds.size, format: format)
let imageBG = renderer.image { context in
///This draws all subviews of the parentView one after the other.
///Because the background image is not a parent of our current view, otherwise childView.drawHierachy would have been enough
for subview in parentView.subviews {
///Skip specific views or view classes you don't want to be added to the image. or if you only need the parentView itself rendered remove the for in loop.
subview.drawHierarchy(in: imageView.bounds, afterScreenUpdates: true)
}
}
//Adding the static background image. This could simply also be the actual image: UIImage if no other views are supposed to be used.
childView.layer.contents = imageBG.cgImage
childView.addSubview(childChildView)
self.view = parentView
}
}
// Present the view controller in the Live View window
PlaygroundPage.current.liveView = MyViewController()
It results in the following:
UPDATE:
The colors in the images are misleading, as you could assume a normal transparency effect would be the same. But the overlayBlendMode is quite different as Coconuts has pointed out. I assume the issue is that the compositingFilter only works with the view below, even if this view is transparent.
I tried finding a workaround by using a mask that cuts out a square of the size of the childchild from the childview. But this also didn't work as the mask is also applied to all subviews. The only way I got it to work is by making the childchildview a sibling of childview instead, or a direct subview of the background view. But not sure if this will be possible in the complex view hierarchy mentioned by Coconuts.
// Sibling view with adjusted x and y
let childChildView = UIView(frame: CGRect(x: 100, y: 100, width: 100, height: 50))
childChildView.backgroundColor = .white
childChildView.layer.compositingFilter = "overlayBlendMode"
parentView.addSubview(childChildView)
MISC:
To only get the visual result of the sample images, not actually using the overlayBlendMode filter as asked by Coconut.
If you only need to blend the color you could change the alpha value of the color.
// Child child view
let childChildView = UIView(frame: CGRect(x: 50, y: 50, width: 100, height: 50))
childChildView.backgroundColor = UIColor(white: 1, alpha: 0.5)
//childChildView.layer.compositingFilter = "overlayBlendMode"
childView.addSubview(childChildView)
Or try this:
// Child child view
let childChildView = UIView(frame: CGRect(x: 50, y: 50, width: 100, height: 50))
childChildView.backgroundColor = .white
childChildView.layer.opacity = 0.5
childView.addSubview(childChildView)
ADDITIONAL ATTEMPTS WHEN HAVING SEVERAL SCROLL VIEWS:
This is an attempt to solve the from Coconut added more complicated view hierarchy with multiple scroll views. The performance needs to be improved or the part that adjusts the background image of the background image layer needs to run in sync when the app is updating (redrawing) its views. At the moment it's lagging behind a bit.
import UIKit
import PlaygroundSupport
class MyViewController : UIViewController {
override func loadView() {
let parentView = UIView()
// Background image
let backgroundImageView = UIImageView(image: UIImage(named: "image.jpg")!)
backgroundImageView.frame = UIScreen.main.bounds
parentView.addSubview(backgroundImageView)
// Page view (horizontal scrollview)
let pageView = UIScrollView(frame: CGRect(x: 50, y: 50, width: 200, height: 200))
pageView.contentSize = CGSize(width: 600, height: 200)
pageView.flashScrollIndicators()
pageView.layer.borderColor = UIColor.yellow.cgColor
pageView.layer.borderWidth = 3
pageView.clipsToBounds = true
parentView.addSubview(pageView)
// Child view (vertical scrollview)
let childView = UIScrollView(frame: CGRect(x: 20, y: 20, width: 100, height: 150))
childView.contentSize = CGSize(width: 100, height: 300)
childView.layer.borderColor = UIColor.red.cgColor
childView.layer.borderWidth = 3
pageView.addSubview(childView)
// Child child view
let childChildView = UIView(frame: CGRect(x: 50, y: 50, width: 50, height: 50))
//Child child view foreground sublayer
let childChildFrontLayer = CALayer()
childChildFrontLayer.frame = childChildView.frame.offsetBy(dx: -75, dy: -50)
childChildFrontLayer.backgroundColor = UIColor.white.cgColor
childChildFrontLayer.compositingFilter = "overlayBlendMode"
//Child child view background sublayer
let childChildBackLayer = CALayer()
childChildBackLayer.contents = UIImage(named: "image.jpg")?.cgImage
var absolutFrame = parentView.convert(childChildView.frame, from: childView)
childChildBackLayer.frame = CGRect(x: -absolutFrame.minX, y: -absolutFrame.minY, width: backgroundImageView.frame.width, height: backgroundImageView.frame.height)
childChildView.layer.addSublayer(childChildBackLayer)
childChildView.layer.addSublayer(childChildFrontLayer)
childView.addSubview(childChildView)
//Checking for any scrolling. Is slightly faster then the scollview delegate methods but might cause main thread checker warning.
DispatchQueue.global(qos: .userInteractive).async {
while true {
if pageView.isDragging || pageView.isTracking || pageView.isDecelerating || childView.isDragging || childView.isTracking || childView.isDecelerating {
absolutFrame = parentView.convert(childChildView.frame, from: childView)
DispatchQueue.main.async {
childChildBackLayer.frame = CGRect(x: -absolutFrame.minX, y: -absolutFrame.minY, width: backgroundImageView.frame.width, height: backgroundImageView.frame.height)
}
}
}
}
self.view = parentView
}
}
// Present the view controller in the Live View window
PlaygroundPage.current.liveView = MyViewController()
Is the issue related to the child view background being clear and therefore 'blending' to give the white colour. Could set the child view background colour to be equal to the app background and then then just blend the childView within the childChildView?
iOS navigation bar custom titleView subviews are not showing properly when the parent viewController pushed from another view controller.
Custom Title view in root view
Custom Title view in View controller after pushing from the different view controller
Code I tried for Custom TitleView
let height = CGFloat(500)
self.navigationController?.navigationBar.frame = CGRect(x: 0, y: 50, width: view.frame.width, height: height)
self.navigationController?.navigationBar.backgroundColor = .blue
let navView = UIImageView()
navView.frame = CGRect(x: 0, y: 0, width: view.frame.width-100, height: height)
navView.backgroundColor = .red
navigationItem.titleView = navView
Suview
let oneLabel = UILabel()
oneLabel.frame = CGRect(x: 50, y: navView.frame.size.height-13, width: 26, height: 26)
oneLabel.text = "1"
oneLabel.textColor = .white
oneLabel.backgroundColor = UIColor(hexString: Constants.greenColor)
oneLabel.textAlignment = .center
navView.addSubview(oneLabel)
oneLabel.clipsToBounds = true
oneLabel.layer.cornerRadius = 13
The total height of the label is 26 and u have reduced only 13 from the imageview's height which shows half of the label.. Make your y value of label something like
y: navView.frame.size.height - 30
I am not able to change the size of my image in the navigation bar for some reason.
Here is my code:
private func setupNavigationBarItems() {
let titleImageView = UIImageView(image: UIImage(named: "radius_image"))
titleImageView.frame = CGRect(x: 0, y: 0, width: 2, height: 2)
titleImageView.contentMode = .scaleAspectFit
navigationItem.titleView = titleImageView
}
It's as if the titleImageView.frame = CGRect(x: 0, y: 0, width: 2, height: 2) line isn't even working.
Haven't found any recent solutions that would help.
UIImageView comes on top of UINavigation Bar title view. In your case you are not changing frame of navigation bar title view.
Using a custom UIView & adding that instance on UINavigationBar item should solve your issue.
let titleView = UIView.init(frame: CGRect.init(x: 0, y: 0, width: 2, height: 2)) // Add your frames
let titleImageView = UIImageView(image: UIImage(named: "radius_image")) // Give your image name
titleImageView.frame = titleView.bounds
titleView.addSubview(titleImageView)
self.navigationItem.titleView = titleView
Having a problem with customizing the navigationBar. I have changed the titleView text to an image through coding. Now I want the image to be a clickable button with a segue to the starting page (for example a home button to the main viewController). How do I make the titleView image into a button (or item) and how do I create a segue from it. I suppose it has to be done in code since I haven't found any other means of doing it. If you have a solution, please be thorough - I'm a beginner.
Simply add a custom UIView containing UIButton and UIImageView to the titleView of navigationItem.
Example:
override func viewDidLoad()
{
super.viewDidLoad()
let button = UIButton(frame: CGRect(x: 0, y: 0, width: self.view.frame.width - 80, height: 30))
button.setTitle("Button", for: .normal)
button.addTarget(self, action: #selector(showMainVC), for: .touchUpInside)
let imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: self.view.frame.width - 80, height: 30))
imageView.image = UIImage(named: "Image")
let customView = UIView(frame: CGRect(x: 0, y: 0, width: self.view.frame.width - 80, height: 30))
customView.addSubview(imageView)
customView.addSubview(button)
self.navigationItem.titleView = customView
}
func showMainVC()
{
//TODO:
}
I would like to add a bottom view in all view controllers which will show the progress of Audio or video play.I need Like this
i did in the following
let window = UIApplication.sharedApplication().keyWindow!
let v = UIView(frame: CGRect(x: window.frame.origin.x, y: window.frame.origin.y, width: window.frame.width, height: window.frame.height))
window.addSubview(v);
v.backgroundColor = UIColor.blackColor()
let v2 = UIView(frame: CGRect(x: 50, y: 50, width: 100, height: 50))
v2.backgroundColor = UIColor.whiteColor()
window.addSubview(v2)
But its not displaying in every screen.
I have an approach for you.
Firstly you have to manage this in global way.
You need to create an Object of AppDelegate. And make a function in that class in which you can code
Then on loading your main view, you need to call that function so it will be visible as you want.