I am trying to use CGRect to make a rectangle shape and then later return it once this works it will allow me to set constraints on it to place it where I want, I am using it inside of this closure and it is giving me an error. This is placed outside of my viewDidLoad
let logo : UIImageView = {
let myLogo = UIImageView(frame: CGRect(x: 0, y: 0, width: self.view.frame.height*(77/812)*2.078, height: self.view.frame.height*(77/812)))
myLogo.image = #imageLiteral(resourceName: "ftrLogo")
return myLogo
}()
You should probably make your logo a lazy var. Do like this:
lazy var logo : UIImageView = {
let myLogo = UIImageView(frame: CGRect(x: 0, y: 0, width: self.view.frame.height*(77/812)*2.078, height: self.view.frame.height*(77/812)))
myLogo.image = #imageLiteral(resourceName: "ftrLogo")
return myLogo
}()
Related
I would like to convert two images into one image with custom design.
The expected image is a snapshot of my UICollectionViewCell, not a UIImage actually.
I copied the layout codes from my custom UICVCell.swift file and tried to render the view into UIImage, but the result image is what you can see below.
I searched through a lot of questions in SOF, but most of it was about 'How you can render a UIView into a UIImage.'
I've tried drawing too, but had the same messed up result.
Can anybody tell me what's the problem?
I would really appreciate your help, it's my first question in SOF.
I might cry in a minute or two, literally...
class ViewController: UIViewController {
private var imageView: UIImageView = {
let iv = UIImageView()
iv.contentMode = .scaleAspectFit
iv.clipsToBounds = true
return iv
}()
func createBubbleImage(images: [UIImage?]) -> UIImage? {
switch images.count {
case 1:
return images[0]
case 2:
let newView = UIView(frame: .init(x: 0, y: 0, width: 300, height: 300))
let size = newView.frame.width
let iv0 = UIImageView(image: images[0])
iv0.contentMode = .scaleAspectFit
iv0.clipsToBounds = true
let iv1 = UIImageView(image: images[1])
iv1.contentMode = .scaleAspectFit
iv1.clipsToBounds = true
newView.addSubview(iv0)
iv0.anchor(top: newView.topAnchor, left: newView.leftAnchor, paddingTop: size * 0.04, paddingLeft: size * 0.04, width: size * 0.56, height: size * 0.56)
newView.addSubview(iv1)
iv1.anchor(bottom: newView.bottomAnchor, right: newView.rightAnchor, paddingBottom: size * 0.04, paddingRight: size * 0.04, width: size * 0.56, height: size * 0.56)
iv0.layer.cornerRadius = size * 0.28
iv1.layer.cornerRadius = size * 0.28
let renderer = UIGraphicsImageRenderer(bounds: newView.bounds)
return renderer.image { ctx in
newView.layer.render(in: ctx.cgContext)
}
default:
return UIImage(named: "logo")
}
}
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
view.addSubview(imageView)
imageView.center(inView: view)
imageView.setDimensions(width: 300, height: 300)
imageView.image = createBubbleImage(images: [UIImage(named: "0"), UIImage(named: "1")])
}
}
Expected Image
Result Image
Do the following:
Remove the code lines that have the anchor method.
Initiate the iv0 and iv1 with UIImageView(frame: ...)
Add iv0.image = images[0] and iv1.image = images[1]
I also increased the radius a little bit because in my test device the images were not completely circular.
The code should look like this:
func createBubbleImage(images: [UIImage?]) -> UIImage? {
switch images.count {
case 1:
return images[0]
case 2:
let newView = UIView(frame: .init(x: 0, y: 0, width: 300, height: 300))
let size = newView.frame.width
let iv0 = UIImageView(frame: .init(x: 0, y: 0, width: 200, height: 200))
iv0.image = images[0]
iv0.contentMode = .scaleAspectFit
iv0.clipsToBounds = true
let iv1 = UIImageView(frame: .init(x: 100, y: 100, width: 200, height: 200))
iv1.image = images[1]
iv1.contentMode = .scaleAspectFit
iv1.clipsToBounds = true
newView.addSubview(iv0)
newView.addSubview(iv1)
iv0.layer.cornerRadius = size * 0.35
iv1.layer.cornerRadius = size * 0.35
let renderer = UIGraphicsImageRenderer(bounds: newView.bounds)
return renderer.image { ctx in
newView.layer.render(in: ctx.cgContext)
}
default:
return UIImage(named: "logo")
}
}
With it, the result is the following:
I had to take a screenshot from your "expected image", that's why the images look like zoomed in. You can play with the UIImageView(frame: ...) part to adjust the two images as you want (I didn't played enough to avoid that cut in the edges, but it is possible with the right measures).
Remark: the x and y values in the UIImageView frame are the horizontal and vertical distances from the top-left corner of newView to the top-left corner of your UIImageView, respectively.
So I added a UIImageView to the right of my UITextField, but I need to add a little bit of padding to the right side so that it doesn't anchor all the way to the right. I tried adding a custom frame but that didn't work, so I'm not too sure how to go about getting that padding. Any help would be much appreaciated.
See TextField Example Here
let titleField : UITextField = {
let titleField = UITextField()
titleField.placeholder = "Title"
titleField.textAlignment = .center
titleField.backgroundColor = .white
titleField.addDoneCancelToolbar()
var imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: 10, height: 10))
let image = UIImage(systemName: "exclamationmark.circle")?.withTintColor(.systemRed, renderingMode: .alwaysOriginal)
imageView.image = image
titleField.rightView = imageView
titleField.rightViewMode = .always
// titleField.rightView?.isHidden = true
return titleField
}()
Subclass UITextField and override https://developer.apple.com/documentation/uikit/uitextfield/1619638-rightviewrect.
Just add the extension :
extension UITextField {
func rightImage(_ image: UIImage?, imageWidth: CGFloat, padding: CGFloat) {
let imageView = UIImageView()
imageView.frame = CGRect(x: padding + 2, y: 0, width: imageWidth, height: frame.height)
imageView.contentMode = .scaleAspectFit
imageView.image = image
let containerView = UIView(frame: CGRect(x: 0, y: 0, width: imageWidth + padding , height: frame.height))
containerView.addSubview(imageView)
rightView = containerView
rightViewMode = .always
}
}
To use it :
if let image = UIImage(named: imagename + ".png") {
titlefield.rightImage(image, imageWidth: 30, padding: 5)
}
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?
I have declared UIImageView using lazy var,
I want to set x position of redArrowImage according to it's superView which will be define at run time.
lazy var redArrowImage: UIImageView = {
let imageView = UIImageView(image: UIImage(named: "red"))
imageView.frame = CGRect(x: self.goButton.frame.size.width - 25, y: 12, width: 15, height: 15)
imageView.contentMode = .scaleAspectFit
return imageView
}()
I have used self.superview.frame.size.width - 25 but it not works :(
imageView.frame = CGRect(x: self.superview.frame.size.width - 25, y: 12, width: 15, height: 15)
Any idea ?
Although it may not be the week to replicate some design of Facebook, I would like to be able to design my own version of the reaction indicator view below.
I have three UIImageViews lined in the same positions as above. The problem is that, unlike Facebook, the background color may change (i.e is on top of a UIBlurEffect) and therefore I am unable to set the border color to white.
I thought it would make sense to set the borderColor like so:
imageViewOne.layer.borderColor = UIColor.clear.cgColor
imageViewOne.layer.borderWidth = 2
However, the underlying imageViewTwo is displayed in the border instead of the background color.
So far, I have this:
Would appreciate some help/ideas on how to make this work - I'm thinking of masks but not sure whether a. this is the correct solution and b. how to achieve the desired effect. To clarify, I am not able to set the border color as a constant as it will change with the UIBlurEffect.
In my opinion, there are 2 way to resolve your problem.
Create and use clipped image for Wow and Love like below Love image.
Another way is using mask property of UIView. Creating a mask image and apply it for mask property.
Mask image looks like.
Code for applying mask.
let imvLoveMask = UIImageView.init(image: UIImage.init(named: "MASK_IMAGE_NAME"));
imvLoveMask.frame = CGRect.init(x: 0, y: 0, width: imvLove.frame.size.width, height: imvLove.frame.size.height);
imvLove.mask = imvLoveMask;
Both of 2 above way can help you achieve what you want in the question. Background of icons in below image is an UIVisualEffectView.
In my opinion, the first way with clipped image is better and faster because you don't need to apply mask for your imageView. But if you don't want to create a clipped image for some reason, you can use the second way.
For more detail, you can take a look at my demo repo
You need to clip part of the image in order to let underlying content be visible in the gaps between images. See playground sample.
Add smile_1, smile_2, smile_3 images to playground resources. I took emoji images from https://emojipedia.org/facebook/.
import UIKit
import PlaygroundSupport
class EmojiView: UIView {
var imageView = UIImageView()
var imageInset = UIEdgeInsets(top: 3.0, left: 3.0, bottom: 3.0, right: 3.0)
var shapeLayer = CAShapeLayer()
var overlap: CGFloat = 0.0 {
didSet {
self.updateShape()
}
}
override init(frame: CGRect) {
super.init(frame: frame)
self.setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
// MARK: UIView
override func awakeFromNib() {
super.awakeFromNib()
self.setup()
}
override func layoutSubviews() {
super.layoutSubviews()
self.imageView.frame = UIEdgeInsetsInsetRect(self.bounds, self.imageInset)
self.shapeLayer.frame = self.bounds
self.updateShape()
}
// MARK: Private
private func setup() {
self.addSubview(self.imageView)
self.layer.mask = self.shapeLayer
}
private func updateShape() {
let frame = self.bounds
let path = UIBezierPath(rect: frame)
// Cut off part of the image if overlap more then > 0
if 0 < self.overlap {
path.usesEvenOddFillRule = true
path.append(UIBezierPath(ovalIn: CGRect(x: -frame.width + self.overlap, y: 0.0, width: frame.width, height: frame.height)).reversing())
}
self.shapeLayer.path = path.cgPath
}
}
let overlap: CGFloat = 10 // Amount of pixels emojis overlap each other
// Create emoji views
let emojiView_1 = EmojiView(frame: CGRect(x: 5.0, y: 5.0, width: 40.0, height: 40.0))
emojiView_1.imageView.image = UIImage(named: "smile_1")
let emojiView_2 = EmojiView(frame: CGRect(x: emojiView_1.frame.maxX - overlap, y: 5.0, width: 40.0, height: 40.0))
emojiView_2.imageView.image = UIImage(named: "smile_2")
emojiView_2.overlap = overlap
let emojiView_3 = EmojiView(frame: CGRect(x: emojiView_2.frame.maxX - overlap, y: 5.0, width: 40.0, height: 40.0))
emojiView_3.imageView.image = UIImage(named: "smile_3")
emojiView_3.overlap = overlap
let holderView = UIView(frame: CGRect(x: 0.0, y: 0.0, width: emojiView_3.frame.maxX + 5, height: 50.0))
// Add gradient layer
let gradientLayer = CAGradientLayer()
gradientLayer.frame = holderView.bounds
gradientLayer.colors = [UIColor.red.cgColor, UIColor.green.cgColor]
gradientLayer.startPoint = CGPoint(x: 0.0, y: 0.5)
gradientLayer.endPoint = CGPoint(x: 1.0, y: 0.5)
holderView.layer.addSublayer(gradientLayer)
// Add emoji views
holderView.addSubview(emojiView_1)
holderView.addSubview(emojiView_2)
holderView.addSubview(emojiView_3)
// Present the view controller in the Live View window
PlaygroundPage.current.liveView = holderView
use this :
self.imageViewOne.layer.cornerRadius = self.imageViewOne.layer.bounds.width/2
self.imageViewOne.layer.masksToBounds = true
Simple suggestion: As you are setting border color programatically, you have a control to change it, according to background color (if background color is solid (not a gradient)).
imageViewOne.layer.borderColor = imageViewOne.superview?.backgroundColor ?? UIColor.white
imageViewOne.layer.borderWidth = 2.0
Actually instead of masking, you can put your images in a view which has white background and round(set corner radius). Then you can put these views (which has white background and images in it) via settings their zPosition or on storyboard with view hierarchy.
I've prepared a little playground for you. You can see the result in the screenshot. I've put a view inside the containerViews instead you can use uiimageview etc. It's a bit ugly but solves your issue I guess it's up to you to decide how use it.
Here is the code, you can just copy and paste it to a new playground and test it.
import UIKit
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true
var mainContainerView = UIView(frame: CGRect(x: 0, y: 0, width: 140, height: 80))
mainContainerView.backgroundColor = UIColor.white
var containerView = UIView(frame: CGRect(x: 0, y: 0, width: 80, height: 80))
containerView.backgroundColor = UIColor.white
containerView.layer.cornerRadius = containerView.frame.width / 2
var innerView = UIView(frame: CGRect(x: 10, y: 10, width: 60, height: 60))
innerView.backgroundColor = UIColor.red
innerView.layer.cornerRadius = innerView.frame.width / 2
containerView.addSubview(innerView)
var containerView2 = UIView(frame: CGRect(x: 60, y: 0, width: 80, height: 80))
containerView2.backgroundColor = UIColor.yellow
containerView2.layer.cornerRadius = containerView2.frame.width / 2
var innerView2 = UIView(frame: CGRect(x: 10, y: 10, width: 60, height: 60))
innerView2.backgroundColor = UIColor.green
innerView2.layer.cornerRadius = innerView2.frame.width / 2
containerView2.addSubview(innerView2)
containerView.layer.zPosition = 2
containerView2.layer.zPosition = 1
mainContainerView.addSubview(containerView)
mainContainerView.addSubview(containerView2)
PlaygroundPage.current.liveView = mainContainerView