kandi background
Explore Kits

CustomImageView | Custom Imageview Circle and Rounded Corner | Android library

 by   SeptiyanAndika Java Version: Current License: No License

 by   SeptiyanAndika Java Version: Current License: No License

Download this library from

kandi X-RAY | CustomImageView Summary

CustomImageView is a Java library typically used in Mobile, Android applications. CustomImageView has no bugs, it has no vulnerabilities and it has low support. However CustomImageView build file is not available. You can download it from GitHub.
Custom Imageview with Rounded Corner an Circle Imageview. ![screenshot cutom image view](https://raw.github.com/SeptiyanAndika/CustomImageView/master/Screenshot.png).
Support
Support
Quality
Quality
Security
Security
License
License
Reuse
Reuse

kandi-support Support

  • CustomImageView has a low active ecosystem.
  • It has 15 star(s) with 6 fork(s). There are 2 watchers for this library.
  • It had no major release in the last 12 months.
  • CustomImageView has no issues reported. There are no pull requests.
  • It has a neutral sentiment in the developer community.
  • The latest version of CustomImageView is current.
CustomImageView Support
Best in #Android
Average in #Android
CustomImageView Support
Best in #Android
Average in #Android

quality kandi Quality

  • CustomImageView has 0 bugs and 0 code smells.
CustomImageView Quality
Best in #Android
Average in #Android
CustomImageView Quality
Best in #Android
Average in #Android

securitySecurity

  • CustomImageView has no vulnerabilities reported, and its dependent libraries have no vulnerabilities reported.
  • CustomImageView code analysis shows 0 unresolved vulnerabilities.
  • There are 0 security hotspots that need review.
CustomImageView Security
Best in #Android
Average in #Android
CustomImageView Security
Best in #Android
Average in #Android

license License

  • CustomImageView does not have a standard license declared.
  • Check the repository for any license declaration and review the terms closely.
  • Without a license, all rights are reserved, and you cannot use the library in your applications.
CustomImageView License
Best in #Android
Average in #Android
CustomImageView License
Best in #Android
Average in #Android

buildReuse

  • CustomImageView releases are not available. You will need to build from source code and install.
  • CustomImageView has no build file. You will be need to create the build yourself to build the component from source.
CustomImageView Reuse
Best in #Android
Average in #Android
CustomImageView Reuse
Best in #Android
Average in #Android
Top functions reviewed by kandi - BETA

kandi has reviewed CustomImageView and discovered the below as its top functions. This is intended to give you an instant insight into CustomImageView implemented functionality, and help decide if they suit your requirements.

  • Renders the bitmap
    • On createOptions menu .
      • Sets the activity to be saved .

        Get all kandi verified functions for this library.

        Get all kandi verified functions for this library.

        CustomImageView Key Features

        Custom Imageview Circle and Rounded Corner

        Save photo SwiftUI

        copy iconCopydownload iconDownload
        UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil)
        
        struct CustomImageView: View {
            var urlString: String
            @ObservedObject var imageLoader: ImageLoaderService
            @State var image: UIImage = UIImage()
            var body: some View {
                Image(uiImage: image)
                    .resizable()
                    .aspectRatio(contentMode: .fit)
                    .frame(width:100, height:100)
                    .onReceive(imageLoader.$image) { image in
                        self.image = image
                    }.onAppear {
                        imageLoader.loadImage(for: urlString)
                    }
            }
        }
        
         class ImageLoaderService: ObservableObject {
            @Published var image: UIImage = UIImage()
            func loadImage(for urlString: String) {
                guard let url = URL(string: urlString) else {return}
                let task = URLSession.shared.dataTask(with: url) { data, response, error in
                    guard let data = data else {return}
                    DispatchQueue.main.async {
                        self.image = UIImage(data: data) ?? UIImage()
                    }
                }
                task.resume()
            }
        }
        
        struct ContentView: View {
            @ObservedObject var imageLoader = ImageLoaderService()
            var body: some View {
                VStack {
                    CustomImageView(urlString: "https://stackoverflow.design/assets/img/logos/so/logo-stackoverflow.png", imageLoader: imageLoader)
                        .contextMenu {
                            Button(action: {
                                UIImageWriteToSavedPhotosAlbum(imageLoader.image, nil, nil, nil)
                            }) {
                                HStack {
                                    Text("Save image")
                                    Image(systemName: "square.and.arrow.down.fill")
        
                                }
                            }
                        }
                }
            }
        }
        
        UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil)
        
        struct CustomImageView: View {
            var urlString: String
            @ObservedObject var imageLoader: ImageLoaderService
            @State var image: UIImage = UIImage()
            var body: some View {
                Image(uiImage: image)
                    .resizable()
                    .aspectRatio(contentMode: .fit)
                    .frame(width:100, height:100)
                    .onReceive(imageLoader.$image) { image in
                        self.image = image
                    }.onAppear {
                        imageLoader.loadImage(for: urlString)
                    }
            }
        }
        
         class ImageLoaderService: ObservableObject {
            @Published var image: UIImage = UIImage()
            func loadImage(for urlString: String) {
                guard let url = URL(string: urlString) else {return}
                let task = URLSession.shared.dataTask(with: url) { data, response, error in
                    guard let data = data else {return}
                    DispatchQueue.main.async {
                        self.image = UIImage(data: data) ?? UIImage()
                    }
                }
                task.resume()
            }
        }
        
        struct ContentView: View {
            @ObservedObject var imageLoader = ImageLoaderService()
            var body: some View {
                VStack {
                    CustomImageView(urlString: "https://stackoverflow.design/assets/img/logos/so/logo-stackoverflow.png", imageLoader: imageLoader)
                        .contextMenu {
                            Button(action: {
                                UIImageWriteToSavedPhotosAlbum(imageLoader.image, nil, nil, nil)
                            }) {
                                HStack {
                                    Text("Save image")
                                    Image(systemName: "square.and.arrow.down.fill")
        
                                }
                            }
                        }
                }
            }
        }
        

        Dismiss modal automatically after 1 sec of being shown - swiftui

        copy iconCopydownload iconDownload
        //to show rejection transition
        .fullScreenCover(isPresented: $isPresented, content: {
             FullScreenModalView.init()
                DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
                      isPresented = false
                }
        
        })
        

        Problems sub-classing a custom ImageView

        copy iconCopydownload iconDownload
        <view class="{package}.{ParentClass}${innerClass}"
        
        <?xml version="1.0" encoding="utf-8"?>
        <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
            xmlns:app="http://schemas.android.com/apk/res-auto"
            xmlns:tools="http://schemas.android.com/tools"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            tools:context=".MainActivity">
            <view class="com.nzeldes.mytestapp.MainActivity$CustomImageView"
                android:id="@+id/imageView"
                android:layout_width="403dp"
                android:layout_height="366dp"
                android:layout_marginTop="72dp"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toTopOf="parent"
                app:srcCompat="@drawable/ic_launcher_background" />
        </androidx.constraintlayout.widget.ConstraintLayout>
        
        <view class="{package}.{ParentClass}${innerClass}"
        
        <?xml version="1.0" encoding="utf-8"?>
        <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
            xmlns:app="http://schemas.android.com/apk/res-auto"
            xmlns:tools="http://schemas.android.com/tools"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            tools:context=".MainActivity">
            <view class="com.nzeldes.mytestapp.MainActivity$CustomImageView"
                android:id="@+id/imageView"
                android:layout_width="403dp"
                android:layout_height="366dp"
                android:layout_marginTop="72dp"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toTopOf="parent"
                app:srcCompat="@drawable/ic_launcher_background" />
        </androidx.constraintlayout.widget.ConstraintLayout>
        

        NSImageView 72dpi image blurry on HiDPI display

        copy iconCopydownload iconDownload
        public class CustomImageView: NSImageView {
            public override func draw(_ dirtyRect: NSRect) {
                if let image = self.image, image.size.width <= dirtyRect.size.width && image.size.height <= dirtyRect.size.height {
                    NSGraphicsContext.current?.imageInterpolation = .none
                }
                super.draw(dirtyRect)
                NSGraphicsContext.current?.imageInterpolation = .default
            }
        }
        

        String Boundingrect calculation issue when string contains '\n'

        copy iconCopydownload iconDownload
        struct MyMessageStruct {
            var time: String = " "
            var name: String = " "
            var profileImageName: String = ""
            var contentImageName: String = ""
            var message: String = " "
        }
        
        class SampleData: NSObject {
            let sampleStrings: [String] = [
                "First message with short text.",
                "Second message with longer text that should cause word wrapping in this cell.",
                "Third message with some embedded newlines.\nThis line comes after a newline (\"\\n\"), so we can see if that works the way we want.",
                "Message without content image.",
                "Longer Message without content image.\n\nWith a pair of embedded newline (\"\\n\") characters giving us a \"blank line\" in the message text.",
                "The sixth message, also without a content image."
            ]
            
            lazy var sampleData: [MyMessageStruct] = [
                MyMessageStruct(time: "08:36", name: "Bob",   profileImageName: "pro1", contentImageName: "content1", message: sampleStrings[0]),
                MyMessageStruct(time: "08:47", name: "Bob",   profileImageName: "pro1", contentImageName: "content2", message: sampleStrings[1]),
                MyMessageStruct(time: "08:59", name: "Joe",   profileImageName: "pro2", contentImageName: "content3", message: sampleStrings[2]),
                MyMessageStruct(time: "09:06", name: "Steve", profileImageName: "pro3", contentImageName:         "", message: sampleStrings[3]),
                MyMessageStruct(time: "09:21", name: "Bob",   profileImageName: "pro1", contentImageName:         "", message: sampleStrings[4]),
                MyMessageStruct(time: "09:45", name: "Joe",   profileImageName: "pro2", contentImageName:         "", message: sampleStrings[5]),
            ]
        }
        
        class ChatTableViewController: UITableViewController {
            
            var myData: [MyMessageStruct] = SampleData().sampleData
            
            override func viewDidLoad() {
                super.viewDidLoad()
        
                // register the cell
                tableView.register(ChatMessageCell.self, forCellReuseIdentifier: "chatCell")
                
                tableView.separatorStyle = .none
                tableView.backgroundView = GrayGradientView()
            }
            
            override func numberOfSections(in tableView: UITableView) -> Int {
                return 1
            }
            override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
                return myData.count
            }
            override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
                let cell = tableView.dequeueReusableCell(withIdentifier: "chatCell", for: indexPath) as! ChatMessageCell
                
                // don't show the profile image if this message is from the same person
                //  as the previous message
                var isSameAuthor = false
                if indexPath.row > 0 {
                    if myData[indexPath.row].name == myData[indexPath.row - 1].name {
                        isSameAuthor = true
                    }
                }
                
                cell.fillData(myData[indexPath.row], isSameAuthor: isSameAuthor)
                
                return cell
            }
            
        }
        
        class ChatMessageCell: UITableViewCell {
            
            let timeLabel = UILabel()
            let nameLabel = UILabel()
            let profileImageView = RoundImageView()
            let bubbleView = CustomRoundedCornerRectangle()
            let stackView = UIStackView()
            let contentImageView = UIImageView()
            let messageLabel = UILabel()
            
            var contentImageHeightConstraint: NSLayoutConstraint!
            
            override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
                super.init(style: style, reuseIdentifier: reuseIdentifier)
                commonInit()
            }
            required init?(coder: NSCoder) {
                super.init(coder: coder)
                commonInit()
            }
            func commonInit() -> Void {
                
                [timeLabel, nameLabel, profileImageView, bubbleView, stackView, contentImageView, messageLabel].forEach {
                    $0.translatesAutoresizingMaskIntoConstraints = false
                }
                
                // MARK: add cell elements
                
                contentView.addSubview(timeLabel)
                contentView.addSubview(nameLabel)
                contentView.addSubview(profileImageView)
                contentView.addSubview(bubbleView)
        
                bubbleView.addSubview(stackView)
                
                stackView.addArrangedSubview(contentImageView)
                stackView.addArrangedSubview(messageLabel)
        
                // MARK: cell element constraints
                
                // make constraints relative to the default cell margins
                let g = contentView.layoutMarginsGuide
                
                NSLayoutConstraint.activate([
                    
                    // timeLabel Top: 0 / Leading: 20
                    timeLabel.topAnchor.constraint(equalTo: g.topAnchor, constant: 0.0),
                    timeLabel.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
                    
                    // nameLabel Top: 0 / Trailing: 30
                    nameLabel.topAnchor.constraint(equalTo: g.topAnchor, constant: 0.0),
                    nameLabel.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -30.0),
                    
                    // profile image
                    //  Top: bubbleView.top + 6
                    profileImageView.topAnchor.constraint(equalTo: bubbleView.topAnchor, constant: 6.0),
                    //  Trailing: 0 (to contentView margin)
                    profileImageView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: 0.0),
                    //  Width: 50 / Height: 1:1 (to keep it square / round)
                    profileImageView.widthAnchor.constraint(equalToConstant: 50.0),
                    profileImageView.heightAnchor.constraint(equalTo: profileImageView.widthAnchor),
                    
                    // bubbleView
                    //  Top: timeLabel.bottom + 4
                    bubbleView.topAnchor.constraint(equalTo: timeLabel.bottomAnchor, constant: 4.0),
                    //  Leading: timeLabel.leading + 16
                    bubbleView.leadingAnchor.constraint(equalTo: timeLabel.leadingAnchor, constant: 16.0),
                    //  Trailing: profile image.leading - 4
                    bubbleView.trailingAnchor.constraint(equalTo: profileImageView.leadingAnchor, constant: -4.0),
                    //  Bottom: contentView.bottom
                    bubbleView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: 0.0),
                    
                    // stackView (to bubbleView)
                    //  Top / Bottom: 12
                    stackView.topAnchor.constraint(equalTo: bubbleView.topAnchor, constant: 12.0),
                    stackView.bottomAnchor.constraint(equalTo: bubbleView.bottomAnchor, constant: -12.0),
                    //  Leading / Trailing: 16
                    stackView.leadingAnchor.constraint(equalTo: bubbleView.leadingAnchor, constant: 16.0),
                    stackView.trailingAnchor.constraint(equalTo: bubbleView.trailingAnchor, constant: -16.0),
        
                ])
                
                // contentImageView height ratio - will be changed based on the loaded image
                // we need to set its Priority to less-than Required or we get auto-layout warnings when the cell is reused
                contentImageHeightConstraint = contentImageView.heightAnchor.constraint(equalTo: contentImageView.widthAnchor, multiplier: 2.0 / 3.0)
                contentImageHeightConstraint.priority = .defaultHigh
                contentImageHeightConstraint.isActive = true
        
                // messageLabel minimum Height: 40
                // we need to set its Priority to less-than Required or we get auto-layout warnings when the cell is reused
                let c = messageLabel.heightAnchor.constraint(greaterThanOrEqualToConstant: 40.0)
                c.priority = .defaultHigh
                c.isActive = true
                
                // MARK: element properties
                
                stackView.axis = .vertical
                stackView.spacing = 6
                
                // set label fonts and alignment here
                timeLabel.font = UIFont.systemFont(ofSize: 14, weight: .regular)
                nameLabel.font = UIFont.systemFont(ofSize: 14, weight: .bold)
                timeLabel.textColor = .gray
                nameLabel.textColor = UIColor(red: 0.175, green: 0.36, blue: 0.72, alpha: 1.0)
                
                // for now, I'm just setting the message label to right-aligned
                //  likely using RTL
                messageLabel.textAlignment = .right
                
                messageLabel.numberOfLines = 0
                
                contentImageView.backgroundColor = .blue
                contentImageView.contentMode = .scaleAspectFit
                contentImageView.layer.cornerRadius = 8
                contentImageView.layer.masksToBounds = true
                
                profileImageView.contentMode = .scaleToFill
                
                // MARK: cell background
                backgroundColor = .clear
                contentView.backgroundColor = .clear
            }
            
            func fillData(_ msg: MyMessageStruct, isSameAuthor: Bool) -> Void {
                timeLabel.text = msg.time
                nameLabel.text = msg.name
                
                nameLabel.isHidden = isSameAuthor
                profileImageView.isHidden = isSameAuthor
                
                if !isSameAuthor {
                    if !msg.profileImageName.isEmpty {
                        if let img = UIImage(named: msg.profileImageName) {
                            profileImageView.image = img
                        }
                    }
                }
                if !msg.contentImageName.isEmpty {
                    contentImageView.isHidden = false
                    if let img = UIImage(named: msg.contentImageName) {
                        contentImageView.image = img
                        let ratio = img.size.height / img.size.width
                        contentImageHeightConstraint.isActive = false
                        contentImageHeightConstraint = contentImageView.heightAnchor.constraint(equalTo: contentImageView.widthAnchor, multiplier: ratio)
                        contentImageHeightConstraint.priority = .defaultHigh
                        contentImageHeightConstraint.isActive = true
                    }
                } else {
                    contentImageView.isHidden = true
                }
                messageLabel.text = msg.message
            }
        }
        
        class CustomRoundedCornerRectangle: UIView {
            lazy var shapeLayer = CAShapeLayer()
            
            override init(frame: CGRect) {
                super.init(frame: frame)
                setup()
            }
            
            required init?(coder: NSCoder) {
                super.init(coder: coder)
                setup()
            }
            
            func setup() {
                // apply properties related to the path
                shapeLayer.fillColor = UIColor.white.cgColor
                shapeLayer.lineWidth = 1.0
                shapeLayer.strokeColor = UIColor(red: 212/255, green: 212/255, blue: 212/255, alpha: 1.0).cgColor
                shapeLayer.position = CGPoint(x: 0, y: 0)
                
                // add the new layer to our custom view
                //self.layer.addSublayer(shapeLayer)
                self.layer.insertSublayer(shapeLayer, at: 0)
            }
            
            override func layoutSubviews() {
                
                let path = UIBezierPath()
                let largeCornerRadius: CGFloat = 18
                let smallCornerRadius: CGFloat = 10
                let upperCornerSpacerRadius: CGFloat = 2
                let imageToArcSpace: CGFloat = 5
                let rect = bounds
                
                // move to starting point
                path.move(to: CGPoint(x: rect.minX + smallCornerRadius, y: rect.maxY))
                
                // draw bottom left corner
                path.addArc(withCenter: CGPoint(x: rect.minX + smallCornerRadius, y: rect.maxY - smallCornerRadius), radius: smallCornerRadius,
                            startAngle: .pi / 2, // straight down
                            endAngle: .pi, // straight left
                            clockwise: true)
                
                // draw left line
                path.addLine(to: CGPoint(x: rect.minX, y: rect.minY + smallCornerRadius))
                
                // draw top left corner
                path.addArc(withCenter: CGPoint(x: rect.minX + smallCornerRadius, y: rect.minY + smallCornerRadius), radius: smallCornerRadius,
                            startAngle: .pi, // straight left
                            endAngle: .pi / 2 * 3, // straight up
                            clockwise: true)
                
                // draw top line
                path.addLine(to: CGPoint(x: rect.maxX - largeCornerRadius, y: rect.minY))
                
                // draw concave top right corner
                // first arc
                path.addArc(withCenter: CGPoint(x: rect.maxX + largeCornerRadius, y: rect.minY + upperCornerSpacerRadius), radius: upperCornerSpacerRadius, startAngle: .pi / 2 * 3, // straight up
                            endAngle: .pi / 2, // straight left
                            clockwise: true)
                
                // second arc
                path.addArc(withCenter: CGPoint(x: rect.maxX + largeCornerRadius + imageToArcSpace, y: rect.minY + largeCornerRadius + upperCornerSpacerRadius * 2 + imageToArcSpace), radius: largeCornerRadius + imageToArcSpace, startAngle: CGFloat(240.0).toRadians(), // up with offset
                            endAngle: .pi, // straight left
                            clockwise: false)
                
                // draw right line
                path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY - smallCornerRadius))
                
                // draw bottom right corner
                path.addArc(withCenter: CGPoint(x: rect.maxX - smallCornerRadius, y: rect.maxY - smallCornerRadius), radius: smallCornerRadius,
                            startAngle: 0, // straight right
                            endAngle: .pi / 2, // straight down
                            clockwise: true)
                
                // draw bottom line to close the shape
                path.close()
                
                shapeLayer.path = path.cgPath
            }
        }
        
        extension CGFloat {
            func toRadians() -> CGFloat {
                return self * CGFloat(Double.pi) / 180.0
            }
        }
        
        class RoundImageView: UIImageView {
            override func layoutSubviews() {
                layer.masksToBounds = true
                layer.cornerRadius = bounds.size.height * 0.5
            }
        }
        
        class GrayGradientView: UIView {
            private var gradLayer: CAGradientLayer!
            
            override class var layerClass: AnyClass {
                return CAGradientLayer.self
            }
            override init(frame: CGRect) {
                super.init(frame: frame)
                commonInit()
            }
            required init?(coder: NSCoder) {
                super.init(coder: coder)
                commonInit()
            }
            func commonInit() -> Void {
                
                let myColors: [UIColor] = [
                    UIColor(white: 0.95, alpha: 1.0),
                    UIColor(white: 0.90, alpha: 1.0),
                ]
                
                gradLayer = self.layer as? CAGradientLayer
                
                // assign the colors (we're using map to convert UIColors to CGColors
                gradLayer.colors = myColors.map({$0.cgColor})
                
                // start at the top
                gradLayer.startPoint = CGPoint(x: 0.25, y: 0.0)
                
                // end at the bottom
                gradLayer.endPoint = CGPoint(x: 0.75, y: 1.0)
                
            }
        }
        
        struct MyMessageStruct {
            var time: String = " "
            var name: String = " "
            var profileImageName: String = ""
            var contentImageName: String = ""
            var message: String = " "
        }
        
        class SampleData: NSObject {
            let sampleStrings: [String] = [
                "First message with short text.",
                "Second message with longer text that should cause word wrapping in this cell.",
                "Third message with some embedded newlines.\nThis line comes after a newline (\"\\n\"), so we can see if that works the way we want.",
                "Message without content image.",
                "Longer Message without content image.\n\nWith a pair of embedded newline (\"\\n\") characters giving us a \"blank line\" in the message text.",
                "The sixth message, also without a content image."
            ]
            
            lazy var sampleData: [MyMessageStruct] = [
                MyMessageStruct(time: "08:36", name: "Bob",   profileImageName: "pro1", contentImageName: "content1", message: sampleStrings[0]),
                MyMessageStruct(time: "08:47", name: "Bob",   profileImageName: "pro1", contentImageName: "content2", message: sampleStrings[1]),
                MyMessageStruct(time: "08:59", name: "Joe",   profileImageName: "pro2", contentImageName: "content3", message: sampleStrings[2]),
                MyMessageStruct(time: "09:06", name: "Steve", profileImageName: "pro3", contentImageName:         "", message: sampleStrings[3]),
                MyMessageStruct(time: "09:21", name: "Bob",   profileImageName: "pro1", contentImageName:         "", message: sampleStrings[4]),
                MyMessageStruct(time: "09:45", name: "Joe",   profileImageName: "pro2", contentImageName:         "", message: sampleStrings[5]),
            ]
        }
        
        class ChatTableViewController: UITableViewController {
            
            var myData: [MyMessageStruct] = SampleData().sampleData
            
            override func viewDidLoad() {
                super.viewDidLoad()
        
                // register the cell
                tableView.register(ChatMessageCell.self, forCellReuseIdentifier: "chatCell")
                
                tableView.separatorStyle = .none
                tableView.backgroundView = GrayGradientView()
            }
            
            override func numberOfSections(in tableView: UITableView) -> Int {
                return 1
            }
            override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
                return myData.count
            }
            override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
                let cell = tableView.dequeueReusableCell(withIdentifier: "chatCell", for: indexPath) as! ChatMessageCell
                
                // don't show the profile image if this message is from the same person
                //  as the previous message
                var isSameAuthor = false
                if indexPath.row > 0 {
                    if myData[indexPath.row].name == myData[indexPath.row - 1].name {
                        isSameAuthor = true
                    }
                }
                
                cell.fillData(myData[indexPath.row], isSameAuthor: isSameAuthor)
                
                return cell
            }
            
        }
        
        class ChatMessageCell: UITableViewCell {
            
            let timeLabel = UILabel()
            let nameLabel = UILabel()
            let profileImageView = RoundImageView()
            let bubbleView = CustomRoundedCornerRectangle()
            let stackView = UIStackView()
            let contentImageView = UIImageView()
            let messageLabel = UILabel()
            
            var contentImageHeightConstraint: NSLayoutConstraint!
            
            override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
                super.init(style: style, reuseIdentifier: reuseIdentifier)
                commonInit()
            }
            required init?(coder: NSCoder) {
                super.init(coder: coder)
                commonInit()
            }
            func commonInit() -> Void {
                
                [timeLabel, nameLabel, profileImageView, bubbleView, stackView, contentImageView, messageLabel].forEach {
                    $0.translatesAutoresizingMaskIntoConstraints = false
                }
                
                // MARK: add cell elements
                
                contentView.addSubview(timeLabel)
                contentView.addSubview(nameLabel)
                contentView.addSubview(profileImageView)
                contentView.addSubview(bubbleView)
        
                bubbleView.addSubview(stackView)
                
                stackView.addArrangedSubview(contentImageView)
                stackView.addArrangedSubview(messageLabel)
        
                // MARK: cell element constraints
                
                // make constraints relative to the default cell margins
                let g = contentView.layoutMarginsGuide
                
                NSLayoutConstraint.activate([
                    
                    // timeLabel Top: 0 / Leading: 20
                    timeLabel.topAnchor.constraint(equalTo: g.topAnchor, constant: 0.0),
                    timeLabel.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
                    
                    // nameLabel Top: 0 / Trailing: 30
                    nameLabel.topAnchor.constraint(equalTo: g.topAnchor, constant: 0.0),
                    nameLabel.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -30.0),
                    
                    // profile image
                    //  Top: bubbleView.top + 6
                    profileImageView.topAnchor.constraint(equalTo: bubbleView.topAnchor, constant: 6.0),
                    //  Trailing: 0 (to contentView margin)
                    profileImageView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: 0.0),
                    //  Width: 50 / Height: 1:1 (to keep it square / round)
                    profileImageView.widthAnchor.constraint(equalToConstant: 50.0),
                    profileImageView.heightAnchor.constraint(equalTo: profileImageView.widthAnchor),
                    
                    // bubbleView
                    //  Top: timeLabel.bottom + 4
                    bubbleView.topAnchor.constraint(equalTo: timeLabel.bottomAnchor, constant: 4.0),
                    //  Leading: timeLabel.leading + 16
                    bubbleView.leadingAnchor.constraint(equalTo: timeLabel.leadingAnchor, constant: 16.0),
                    //  Trailing: profile image.leading - 4
                    bubbleView.trailingAnchor.constraint(equalTo: profileImageView.leadingAnchor, constant: -4.0),
                    //  Bottom: contentView.bottom
                    bubbleView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: 0.0),
                    
                    // stackView (to bubbleView)
                    //  Top / Bottom: 12
                    stackView.topAnchor.constraint(equalTo: bubbleView.topAnchor, constant: 12.0),
                    stackView.bottomAnchor.constraint(equalTo: bubbleView.bottomAnchor, constant: -12.0),
                    //  Leading / Trailing: 16
                    stackView.leadingAnchor.constraint(equalTo: bubbleView.leadingAnchor, constant: 16.0),
                    stackView.trailingAnchor.constraint(equalTo: bubbleView.trailingAnchor, constant: -16.0),
        
                ])
                
                // contentImageView height ratio - will be changed based on the loaded image
                // we need to set its Priority to less-than Required or we get auto-layout warnings when the cell is reused
                contentImageHeightConstraint = contentImageView.heightAnchor.constraint(equalTo: contentImageView.widthAnchor, multiplier: 2.0 / 3.0)
                contentImageHeightConstraint.priority = .defaultHigh
                contentImageHeightConstraint.isActive = true
        
                // messageLabel minimum Height: 40
                // we need to set its Priority to less-than Required or we get auto-layout warnings when the cell is reused
                let c = messageLabel.heightAnchor.constraint(greaterThanOrEqualToConstant: 40.0)
                c.priority = .defaultHigh
                c.isActive = true
                
                // MARK: element properties
                
                stackView.axis = .vertical
                stackView.spacing = 6
                
                // set label fonts and alignment here
                timeLabel.font = UIFont.systemFont(ofSize: 14, weight: .regular)
                nameLabel.font = UIFont.systemFont(ofSize: 14, weight: .bold)
                timeLabel.textColor = .gray
                nameLabel.textColor = UIColor(red: 0.175, green: 0.36, blue: 0.72, alpha: 1.0)
                
                // for now, I'm just setting the message label to right-aligned
                //  likely using RTL
                messageLabel.textAlignment = .right
                
                messageLabel.numberOfLines = 0
                
                contentImageView.backgroundColor = .blue
                contentImageView.contentMode = .scaleAspectFit
                contentImageView.layer.cornerRadius = 8
                contentImageView.layer.masksToBounds = true
                
                profileImageView.contentMode = .scaleToFill
                
                // MARK: cell background
                backgroundColor = .clear
                contentView.backgroundColor = .clear
            }
            
            func fillData(_ msg: MyMessageStruct, isSameAuthor: Bool) -> Void {
                timeLabel.text = msg.time
                nameLabel.text = msg.name
                
                nameLabel.isHidden = isSameAuthor
                profileImageView.isHidden = isSameAuthor
                
                if !isSameAuthor {
                    if !msg.profileImageName.isEmpty {
                        if let img = UIImage(named: msg.profileImageName) {
                            profileImageView.image = img
                        }
                    }
                }
                if !msg.contentImageName.isEmpty {
                    contentImageView.isHidden = false
                    if let img = UIImage(named: msg.contentImageName) {
                        contentImageView.image = img
                        let ratio = img.size.height / img.size.width
                        contentImageHeightConstraint.isActive = false
                        contentImageHeightConstraint = contentImageView.heightAnchor.constraint(equalTo: contentImageView.widthAnchor, multiplier: ratio)
                        contentImageHeightConstraint.priority = .defaultHigh
                        contentImageHeightConstraint.isActive = true
                    }
                } else {
                    contentImageView.isHidden = true
                }
                messageLabel.text = msg.message
            }
        }
        
        class CustomRoundedCornerRectangle: UIView {
            lazy var shapeLayer = CAShapeLayer()
            
            override init(frame: CGRect) {
                super.init(frame: frame)
                setup()
            }
            
            required init?(coder: NSCoder) {
                super.init(coder: coder)
                setup()
            }
            
            func setup() {
                // apply properties related to the path
                shapeLayer.fillColor = UIColor.white.cgColor
                shapeLayer.lineWidth = 1.0
                shapeLayer.strokeColor = UIColor(red: 212/255, green: 212/255, blue: 212/255, alpha: 1.0).cgColor
                shapeLayer.position = CGPoint(x: 0, y: 0)
                
                // add the new layer to our custom view
                //self.layer.addSublayer(shapeLayer)
                self.layer.insertSublayer(shapeLayer, at: 0)
            }
            
            override func layoutSubviews() {
                
                let path = UIBezierPath()
                let largeCornerRadius: CGFloat = 18
                let smallCornerRadius: CGFloat = 10
                let upperCornerSpacerRadius: CGFloat = 2
                let imageToArcSpace: CGFloat = 5
                let rect = bounds
                
                // move to starting point
                path.move(to: CGPoint(x: rect.minX + smallCornerRadius, y: rect.maxY))
                
                // draw bottom left corner
                path.addArc(withCenter: CGPoint(x: rect.minX + smallCornerRadius, y: rect.maxY - smallCornerRadius), radius: smallCornerRadius,
                            startAngle: .pi / 2, // straight down
                            endAngle: .pi, // straight left
                            clockwise: true)
                
                // draw left line
                path.addLine(to: CGPoint(x: rect.minX, y: rect.minY + smallCornerRadius))
                
                // draw top left corner
                path.addArc(withCenter: CGPoint(x: rect.minX + smallCornerRadius, y: rect.minY + smallCornerRadius), radius: smallCornerRadius,
                            startAngle: .pi, // straight left
                            endAngle: .pi / 2 * 3, // straight up
                            clockwise: true)
                
                // draw top line
                path.addLine(to: CGPoint(x: rect.maxX - largeCornerRadius, y: rect.minY))
                
                // draw concave top right corner
                // first arc
                path.addArc(withCenter: CGPoint(x: rect.maxX + largeCornerRadius, y: rect.minY + upperCornerSpacerRadius), radius: upperCornerSpacerRadius, startAngle: .pi / 2 * 3, // straight up
                            endAngle: .pi / 2, // straight left
                            clockwise: true)
                
                // second arc
                path.addArc(withCenter: CGPoint(x: rect.maxX + largeCornerRadius + imageToArcSpace, y: rect.minY + largeCornerRadius + upperCornerSpacerRadius * 2 + imageToArcSpace), radius: largeCornerRadius + imageToArcSpace, startAngle: CGFloat(240.0).toRadians(), // up with offset
                            endAngle: .pi, // straight left
                            clockwise: false)
                
                // draw right line
                path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY - smallCornerRadius))
                
                // draw bottom right corner
                path.addArc(withCenter: CGPoint(x: rect.maxX - smallCornerRadius, y: rect.maxY - smallCornerRadius), radius: smallCornerRadius,
                            startAngle: 0, // straight right
                            endAngle: .pi / 2, // straight down
                            clockwise: true)
                
                // draw bottom line to close the shape
                path.close()
                
                shapeLayer.path = path.cgPath
            }
        }
        
        extension CGFloat {
            func toRadians() -> CGFloat {
                return self * CGFloat(Double.pi) / 180.0
            }
        }
        
        class RoundImageView: UIImageView {
            override func layoutSubviews() {
                layer.masksToBounds = true
                layer.cornerRadius = bounds.size.height * 0.5
            }
        }
        
        class GrayGradientView: UIView {
            private var gradLayer: CAGradientLayer!
            
            override class var layerClass: AnyClass {
                return CAGradientLayer.self
            }
            override init(frame: CGRect) {
                super.init(frame: frame)
                commonInit()
            }
            required init?(coder: NSCoder) {
                super.init(coder: coder)
                commonInit()
            }
            func commonInit() -> Void {
                
                let myColors: [UIColor] = [
                    UIColor(white: 0.95, alpha: 1.0),
                    UIColor(white: 0.90, alpha: 1.0),
                ]
                
                gradLayer = self.layer as? CAGradientLayer
                
                // assign the colors (we're using map to convert UIColors to CGColors
                gradLayer.colors = myColors.map({$0.cgColor})
                
                // start at the top
                gradLayer.startPoint = CGPoint(x: 0.25, y: 0.0)
                
                // end at the bottom
                gradLayer.endPoint = CGPoint(x: 0.75, y: 1.0)
                
            }
        }
        
        struct MyMessageStruct {
            var time: String = " "
            var name: String = " "
            var profileImageName: String = ""
            var contentImageName: String = ""
            var message: String = " "
        }
        
        class SampleData: NSObject {
            let sampleStrings: [String] = [
                "First message with short text.",
                "Second message with longer text that should cause word wrapping in this cell.",
                "Third message with some embedded newlines.\nThis line comes after a newline (\"\\n\"), so we can see if that works the way we want.",
                "Message without content image.",
                "Longer Message without content image.\n\nWith a pair of embedded newline (\"\\n\") characters giving us a \"blank line\" in the message text.",
                "The sixth message, also without a content image."
            ]
            
            lazy var sampleData: [MyMessageStruct] = [
                MyMessageStruct(time: "08:36", name: "Bob",   profileImageName: "pro1", contentImageName: "content1", message: sampleStrings[0]),
                MyMessageStruct(time: "08:47", name: "Bob",   profileImageName: "pro1", contentImageName: "content2", message: sampleStrings[1]),
                MyMessageStruct(time: "08:59", name: "Joe",   profileImageName: "pro2", contentImageName: "content3", message: sampleStrings[2]),
                MyMessageStruct(time: "09:06", name: "Steve", profileImageName: "pro3", contentImageName:         "", message: sampleStrings[3]),
                MyMessageStruct(time: "09:21", name: "Bob",   profileImageName: "pro1", contentImageName:         "", message: sampleStrings[4]),
                MyMessageStruct(time: "09:45", name: "Joe",   profileImageName: "pro2", contentImageName:         "", message: sampleStrings[5]),
            ]
        }
        
        class ChatTableViewController: UITableViewController {
            
            var myData: [MyMessageStruct] = SampleData().sampleData
            
            override func viewDidLoad() {
                super.viewDidLoad()
        
                // register the cell
                tableView.register(ChatMessageCell.self, forCellReuseIdentifier: "chatCell")
                
                tableView.separatorStyle = .none
                tableView.backgroundView = GrayGradientView()
            }
            
            override func numberOfSections(in tableView: UITableView) -> Int {
                return 1
            }
            override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
                return myData.count
            }
            override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
                let cell = tableView.dequeueReusableCell(withIdentifier: "chatCell", for: indexPath) as! ChatMessageCell
                
                // don't show the profile image if this message is from the same person
                //  as the previous message
                var isSameAuthor = false
                if indexPath.row > 0 {
                    if myData[indexPath.row].name == myData[indexPath.row - 1].name {
                        isSameAuthor = true
                    }
                }
                
                cell.fillData(myData[indexPath.row], isSameAuthor: isSameAuthor)
                
                return cell
            }
            
        }
        
        class ChatMessageCell: UITableViewCell {
            
            let timeLabel = UILabel()
            let nameLabel = UILabel()
            let profileImageView = RoundImageView()
            let bubbleView = CustomRoundedCornerRectangle()
            let stackView = UIStackView()
            let contentImageView = UIImageView()
            let messageLabel = UILabel()
            
            var contentImageHeightConstraint: NSLayoutConstraint!
            
            override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
                super.init(style: style, reuseIdentifier: reuseIdentifier)
                commonInit()
            }
            required init?(coder: NSCoder) {
                super.init(coder: coder)
                commonInit()
            }
            func commonInit() -> Void {
                
                [timeLabel, nameLabel, profileImageView, bubbleView, stackView, contentImageView, messageLabel].forEach {
                    $0.translatesAutoresizingMaskIntoConstraints = false
                }
                
                // MARK: add cell elements
                
                contentView.addSubview(timeLabel)
                contentView.addSubview(nameLabel)
                contentView.addSubview(profileImageView)
                contentView.addSubview(bubbleView)
        
                bubbleView.addSubview(stackView)
                
                stackView.addArrangedSubview(contentImageView)
                stackView.addArrangedSubview(messageLabel)
        
                // MARK: cell element constraints
                
                // make constraints relative to the default cell margins
                let g = contentView.layoutMarginsGuide
                
                NSLayoutConstraint.activate([
                    
                    // timeLabel Top: 0 / Leading: 20
                    timeLabel.topAnchor.constraint(equalTo: g.topAnchor, constant: 0.0),
                    timeLabel.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
                    
                    // nameLabel Top: 0 / Trailing: 30
                    nameLabel.topAnchor.constraint(equalTo: g.topAnchor, constant: 0.0),
                    nameLabel.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -30.0),
                    
                    // profile image
                    //  Top: bubbleView.top + 6
                    profileImageView.topAnchor.constraint(equalTo: bubbleView.topAnchor, constant: 6.0),
                    //  Trailing: 0 (to contentView margin)
                    profileImageView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: 0.0),
                    //  Width: 50 / Height: 1:1 (to keep it square / round)
                    profileImageView.widthAnchor.constraint(equalToConstant: 50.0),
                    profileImageView.heightAnchor.constraint(equalTo: profileImageView.widthAnchor),
                    
                    // bubbleView
                    //  Top: timeLabel.bottom + 4
                    bubbleView.topAnchor.constraint(equalTo: timeLabel.bottomAnchor, constant: 4.0),
                    //  Leading: timeLabel.leading + 16
                    bubbleView.leadingAnchor.constraint(equalTo: timeLabel.leadingAnchor, constant: 16.0),
                    //  Trailing: profile image.leading - 4
                    bubbleView.trailingAnchor.constraint(equalTo: profileImageView.leadingAnchor, constant: -4.0),
                    //  Bottom: contentView.bottom
                    bubbleView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: 0.0),
                    
                    // stackView (to bubbleView)
                    //  Top / Bottom: 12
                    stackView.topAnchor.constraint(equalTo: bubbleView.topAnchor, constant: 12.0),
                    stackView.bottomAnchor.constraint(equalTo: bubbleView.bottomAnchor, constant: -12.0),
                    //  Leading / Trailing: 16
                    stackView.leadingAnchor.constraint(equalTo: bubbleView.leadingAnchor, constant: 16.0),
                    stackView.trailingAnchor.constraint(equalTo: bubbleView.trailingAnchor, constant: -16.0),
        
                ])
                
                // contentImageView height ratio - will be changed based on the loaded image
                // we need to set its Priority to less-than Required or we get auto-layout warnings when the cell is reused
                contentImageHeightConstraint = contentImageView.heightAnchor.constraint(equalTo: contentImageView.widthAnchor, multiplier: 2.0 / 3.0)
                contentImageHeightConstraint.priority = .defaultHigh
                contentImageHeightConstraint.isActive = true
        
                // messageLabel minimum Height: 40
                // we need to set its Priority to less-than Required or we get auto-layout warnings when the cell is reused
                let c = messageLabel.heightAnchor.constraint(greaterThanOrEqualToConstant: 40.0)
                c.priority = .defaultHigh
                c.isActive = true
                
                // MARK: element properties
                
                stackView.axis = .vertical
                stackView.spacing = 6
                
                // set label fonts and alignment here
                timeLabel.font = UIFont.systemFont(ofSize: 14, weight: .regular)
                nameLabel.font = UIFont.systemFont(ofSize: 14, weight: .bold)
                timeLabel.textColor = .gray
                nameLabel.textColor = UIColor(red: 0.175, green: 0.36, blue: 0.72, alpha: 1.0)
                
                // for now, I'm just setting the message label to right-aligned
                //  likely using RTL
                messageLabel.textAlignment = .right
                
                messageLabel.numberOfLines = 0
                
                contentImageView.backgroundColor = .blue
                contentImageView.contentMode = .scaleAspectFit
                contentImageView.layer.cornerRadius = 8
                contentImageView.layer.masksToBounds = true
                
                profileImageView.contentMode = .scaleToFill
                
                // MARK: cell background
                backgroundColor = .clear
                contentView.backgroundColor = .clear
            }
            
            func fillData(_ msg: MyMessageStruct, isSameAuthor: Bool) -> Void {
                timeLabel.text = msg.time
                nameLabel.text = msg.name
                
                nameLabel.isHidden = isSameAuthor
                profileImageView.isHidden = isSameAuthor
                
                if !isSameAuthor {
                    if !msg.profileImageName.isEmpty {
                        if let img = UIImage(named: msg.profileImageName) {
                            profileImageView.image = img
                        }
                    }
                }
                if !msg.contentImageName.isEmpty {
                    contentImageView.isHidden = false
                    if let img = UIImage(named: msg.contentImageName) {
                        contentImageView.image = img
                        let ratio = img.size.height / img.size.width
                        contentImageHeightConstraint.isActive = false
                        contentImageHeightConstraint = contentImageView.heightAnchor.constraint(equalTo: contentImageView.widthAnchor, multiplier: ratio)
                        contentImageHeightConstraint.priority = .defaultHigh
                        contentImageHeightConstraint.isActive = true
                    }
                } else {
                    contentImageView.isHidden = true
                }
                messageLabel.text = msg.message
            }
        }
        
        class CustomRoundedCornerRectangle: UIView {
            lazy var shapeLayer = CAShapeLayer()
            
            override init(frame: CGRect) {
                super.init(frame: frame)
                setup()
            }
            
            required init?(coder: NSCoder) {
                super.init(coder: coder)
                setup()
            }
            
            func setup() {
                // apply properties related to the path
                shapeLayer.fillColor = UIColor.white.cgColor
                shapeLayer.lineWidth = 1.0
                shapeLayer.strokeColor = UIColor(red: 212/255, green: 212/255, blue: 212/255, alpha: 1.0).cgColor
                shapeLayer.position = CGPoint(x: 0, y: 0)
                
                // add the new layer to our custom view
                //self.layer.addSublayer(shapeLayer)
                self.layer.insertSublayer(shapeLayer, at: 0)
            }
            
            override func layoutSubviews() {
                
                let path = UIBezierPath()
                let largeCornerRadius: CGFloat = 18
                let smallCornerRadius: CGFloat = 10
                let upperCornerSpacerRadius: CGFloat = 2
                let imageToArcSpace: CGFloat = 5
                let rect = bounds
                
                // move to starting point
                path.move(to: CGPoint(x: rect.minX + smallCornerRadius, y: rect.maxY))
                
                // draw bottom left corner
                path.addArc(withCenter: CGPoint(x: rect.minX + smallCornerRadius, y: rect.maxY - smallCornerRadius), radius: smallCornerRadius,
                            startAngle: .pi / 2, // straight down
                            endAngle: .pi, // straight left
                            clockwise: true)
                
                // draw left line
                path.addLine(to: CGPoint(x: rect.minX, y: rect.minY + smallCornerRadius))
                
                // draw top left corner
                path.addArc(withCenter: CGPoint(x: rect.minX + smallCornerRadius, y: rect.minY + smallCornerRadius), radius: smallCornerRadius,
                            startAngle: .pi, // straight left
                            endAngle: .pi / 2 * 3, // straight up
                            clockwise: true)
                
                // draw top line
                path.addLine(to: CGPoint(x: rect.maxX - largeCornerRadius, y: rect.minY))
                
                // draw concave top right corner
                // first arc
                path.addArc(withCenter: CGPoint(x: rect.maxX + largeCornerRadius, y: rect.minY + upperCornerSpacerRadius), radius: upperCornerSpacerRadius, startAngle: .pi / 2 * 3, // straight up
                            endAngle: .pi / 2, // straight left
                            clockwise: true)
                
                // second arc
                path.addArc(withCenter: CGPoint(x: rect.maxX + largeCornerRadius + imageToArcSpace, y: rect.minY + largeCornerRadius + upperCornerSpacerRadius * 2 + imageToArcSpace), radius: largeCornerRadius + imageToArcSpace, startAngle: CGFloat(240.0).toRadians(), // up with offset
                            endAngle: .pi, // straight left
                            clockwise: false)
                
                // draw right line
                path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY - smallCornerRadius))
                
                // draw bottom right corner
                path.addArc(withCenter: CGPoint(x: rect.maxX - smallCornerRadius, y: rect.maxY - smallCornerRadius), radius: smallCornerRadius,
                            startAngle: 0, // straight right
                            endAngle: .pi / 2, // straight down
                            clockwise: true)
                
                // draw bottom line to close the shape
                path.close()
                
                shapeLayer.path = path.cgPath
            }
        }
        
        extension CGFloat {
            func toRadians() -> CGFloat {
                return self * CGFloat(Double.pi) / 180.0
            }
        }
        
        class RoundImageView: UIImageView {
            override func layoutSubviews() {
                layer.masksToBounds = true
                layer.cornerRadius = bounds.size.height * 0.5
            }
        }
        
        class GrayGradientView: UIView {
            private var gradLayer: CAGradientLayer!
            
            override class var layerClass: AnyClass {
                return CAGradientLayer.self
            }
            override init(frame: CGRect) {
                super.init(frame: frame)
                commonInit()
            }
            required init?(coder: NSCoder) {
                super.init(coder: coder)
                commonInit()
            }
            func commonInit() -> Void {
                
                let myColors: [UIColor] = [
                    UIColor(white: 0.95, alpha: 1.0),
                    UIColor(white: 0.90, alpha: 1.0),
                ]
                
                gradLayer = self.layer as? CAGradientLayer
                
                // assign the colors (we're using map to convert UIColors to CGColors
                gradLayer.colors = myColors.map({$0.cgColor})
                
                // start at the top
                gradLayer.startPoint = CGPoint(x: 0.25, y: 0.0)
                
                // end at the bottom
                gradLayer.endPoint = CGPoint(x: 0.75, y: 1.0)
                
            }
        }
        
        struct MyMessageStruct {
            var time: String = " "
            var name: String = " "
            var profileImageName: String = ""
            var contentImageName: String = ""
            var message: String = " "
        }
        
        class SampleData: NSObject {
            let sampleStrings: [String] = [
                "First message with short text.",
                "Second message with longer text that should cause word wrapping in this cell.",
                "Third message with some embedded newlines.\nThis line comes after a newline (\"\\n\"), so we can see if that works the way we want.",
                "Message without content image.",
                "Longer Message without content image.\n\nWith a pair of embedded newline (\"\\n\") characters giving us a \"blank line\" in the message text.",
                "The sixth message, also without a content image."
            ]
            
            lazy var sampleData: [MyMessageStruct] = [
                MyMessageStruct(time: "08:36", name: "Bob",   profileImageName: "pro1", contentImageName: "content1", message: sampleStrings[0]),
                MyMessageStruct(time: "08:47", name: "Bob",   profileImageName: "pro1", contentImageName: "content2", message: sampleStrings[1]),
                MyMessageStruct(time: "08:59", name: "Joe",   profileImageName: "pro2", contentImageName: "content3", message: sampleStrings[2]),
                MyMessageStruct(time: "09:06", name: "Steve", profileImageName: "pro3", contentImageName:         "", message: sampleStrings[3]),
                MyMessageStruct(time: "09:21", name: "Bob",   profileImageName: "pro1", contentImageName:         "", message: sampleStrings[4]),
                MyMessageStruct(time: "09:45", name: "Joe",   profileImageName: "pro2", contentImageName:         "", message: sampleStrings[5]),
            ]
        }
        
        class ChatTableViewController: UITableViewController {
            
            var myData: [MyMessageStruct] = SampleData().sampleData
            
            override func viewDidLoad() {
                super.viewDidLoad()
        
                // register the cell
                tableView.register(ChatMessageCell.self, forCellReuseIdentifier: "chatCell")
                
                tableView.separatorStyle = .none
                tableView.backgroundView = GrayGradientView()
            }
            
            override func numberOfSections(in tableView: UITableView) -> Int {
                return 1
            }
            override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
                return myData.count
            }
            override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
                let cell = tableView.dequeueReusableCell(withIdentifier: "chatCell", for: indexPath) as! ChatMessageCell
                
                // don't show the profile image if this message is from the same person
                //  as the previous message
                var isSameAuthor = false
                if indexPath.row > 0 {
                    if myData[indexPath.row].name == myData[indexPath.row - 1].name {
                        isSameAuthor = true
                    }
                }
                
                cell.fillData(myData[indexPath.row], isSameAuthor: isSameAuthor)
                
                return cell
            }
            
        }
        
        class ChatMessageCell: UITableViewCell {
            
            let timeLabel = UILabel()
            let nameLabel = UILabel()
            let profileImageView = RoundImageView()
            let bubbleView = CustomRoundedCornerRectangle()
            let stackView = UIStackView()
            let contentImageView = UIImageView()
            let messageLabel = UILabel()
            
            var contentImageHeightConstraint: NSLayoutConstraint!
            
            override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
                super.init(style: style, reuseIdentifier: reuseIdentifier)
                commonInit()
            }
            required init?(coder: NSCoder) {
                super.init(coder: coder)
                commonInit()
            }
            func commonInit() -> Void {
                
                [timeLabel, nameLabel, profileImageView, bubbleView, stackView, contentImageView, messageLabel].forEach {
                    $0.translatesAutoresizingMaskIntoConstraints = false
                }
                
                // MARK: add cell elements
                
                contentView.addSubview(timeLabel)
                contentView.addSubview(nameLabel)
                contentView.addSubview(profileImageView)
                contentView.addSubview(bubbleView)
        
                bubbleView.addSubview(stackView)
                
                stackView.addArrangedSubview(contentImageView)
                stackView.addArrangedSubview(messageLabel)
        
                // MARK: cell element constraints
                
                // make constraints relative to the default cell margins
                let g = contentView.layoutMarginsGuide
                
                NSLayoutConstraint.activate([
                    
                    // timeLabel Top: 0 / Leading: 20
                    timeLabel.topAnchor.constraint(equalTo: g.topAnchor, constant: 0.0),
                    timeLabel.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
                    
                    // nameLabel Top: 0 / Trailing: 30
                    nameLabel.topAnchor.constraint(equalTo: g.topAnchor, constant: 0.0),
                    nameLabel.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -30.0),
                    
                    // profile image
                    //  Top: bubbleView.top + 6
                    profileImageView.topAnchor.constraint(equalTo: bubbleView.topAnchor, constant: 6.0),
                    //  Trailing: 0 (to contentView margin)
                    profileImageView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: 0.0),
                    //  Width: 50 / Height: 1:1 (to keep it square / round)
                    profileImageView.widthAnchor.constraint(equalToConstant: 50.0),
                    profileImageView.heightAnchor.constraint(equalTo: profileImageView.widthAnchor),
                    
                    // bubbleView
                    //  Top: timeLabel.bottom + 4
                    bubbleView.topAnchor.constraint(equalTo: timeLabel.bottomAnchor, constant: 4.0),
                    //  Leading: timeLabel.leading + 16
                    bubbleView.leadingAnchor.constraint(equalTo: timeLabel.leadingAnchor, constant: 16.0),
                    //  Trailing: profile image.leading - 4
                    bubbleView.trailingAnchor.constraint(equalTo: profileImageView.leadingAnchor, constant: -4.0),
                    //  Bottom: contentView.bottom
                    bubbleView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: 0.0),
                    
                    // stackView (to bubbleView)
                    //  Top / Bottom: 12
                    stackView.topAnchor.constraint(equalTo: bubbleView.topAnchor, constant: 12.0),
                    stackView.bottomAnchor.constraint(equalTo: bubbleView.bottomAnchor, constant: -12.0),
                    //  Leading / Trailing: 16
                    stackView.leadingAnchor.constraint(equalTo: bubbleView.leadingAnchor, constant: 16.0),
                    stackView.trailingAnchor.constraint(equalTo: bubbleView.trailingAnchor, constant: -16.0),
        
                ])
                
                // contentImageView height ratio - will be changed based on the loaded image
                // we need to set its Priority to less-than Required or we get auto-layout warnings when the cell is reused
                contentImageHeightConstraint = contentImageView.heightAnchor.constraint(equalTo: contentImageView.widthAnchor, multiplier: 2.0 / 3.0)
                contentImageHeightConstraint.priority = .defaultHigh
                contentImageHeightConstraint.isActive = true
        
                // messageLabel minimum Height: 40
                // we need to set its Priority to less-than Required or we get auto-layout warnings when the cell is reused
                let c = messageLabel.heightAnchor.constraint(greaterThanOrEqualToConstant: 40.0)
                c.priority = .defaultHigh
                c.isActive = true
                
                // MARK: element properties
                
                stackView.axis = .vertical
                stackView.spacing = 6
                
                // set label fonts and alignment here
                timeLabel.font = UIFont.systemFont(ofSize: 14, weight: .regular)
                nameLabel.font = UIFont.systemFont(ofSize: 14, weight: .bold)
                timeLabel.textColor = .gray
                nameLabel.textColor = UIColor(red: 0.175, green: 0.36, blue: 0.72, alpha: 1.0)
                
                // for now, I'm just setting the message label to right-aligned
                //  likely using RTL
                messageLabel.textAlignment = .right
                
                messageLabel.numberOfLines = 0
                
                contentImageView.backgroundColor = .blue
                contentImageView.contentMode = .scaleAspectFit
                contentImageView.layer.cornerRadius = 8
                contentImageView.layer.masksToBounds = true
                
                profileImageView.contentMode = .scaleToFill
                
                // MARK: cell background
                backgroundColor = .clear
                contentView.backgroundColor = .clear
            }
            
            func fillData(_ msg: MyMessageStruct, isSameAuthor: Bool) -> Void {
                timeLabel.text = msg.time
                nameLabel.text = msg.name
                
                nameLabel.isHidden = isSameAuthor
                profileImageView.isHidden = isSameAuthor
                
                if !isSameAuthor {
                    if !msg.profileImageName.isEmpty {
                        if let img = UIImage(named: msg.profileImageName) {
                            profileImageView.image = img
                        }
                    }
                }
                if !msg.contentImageName.isEmpty {
                    contentImageView.isHidden = false
                    if let img = UIImage(named: msg.contentImageName) {
                        contentImageView.image = img
                        let ratio = img.size.height / img.size.width
                        contentImageHeightConstraint.isActive = false
                        contentImageHeightConstraint = contentImageView.heightAnchor.constraint(equalTo: contentImageView.widthAnchor, multiplier: ratio)
                        contentImageHeightConstraint.priority = .defaultHigh
                        contentImageHeightConstraint.isActive = true
                    }
                } else {
                    contentImageView.isHidden = true
                }
                messageLabel.text = msg.message
            }
        }
        
        class CustomRoundedCornerRectangle: UIView {
            lazy var shapeLayer = CAShapeLayer()
            
            override init(frame: CGRect) {
                super.init(frame: frame)
                setup()
            }
            
            required init?(coder: NSCoder) {
                super.init(coder: coder)
                setup()
            }
            
            func setup() {
                // apply properties related to the path
                shapeLayer.fillColor = UIColor.white.cgColor
                shapeLayer.lineWidth = 1.0
                shapeLayer.strokeColor = UIColor(red: 212/255, green: 212/255, blue: 212/255, alpha: 1.0).cgColor
                shapeLayer.position = CGPoint(x: 0, y: 0)
                
                // add the new layer to our custom view
                //self.layer.addSublayer(shapeLayer)
                self.layer.insertSublayer(shapeLayer, at: 0)
            }
            
            override func layoutSubviews() {
                
                let path = UIBezierPath()
                let largeCornerRadius: CGFloat = 18
                let smallCornerRadius: CGFloat = 10
                let upperCornerSpacerRadius: CGFloat = 2
                let imageToArcSpace: CGFloat = 5
                let rect = bounds
                
                // move to starting point
                path.move(to: CGPoint(x: rect.minX + smallCornerRadius, y: rect.maxY))
                
                // draw bottom left corner
                path.addArc(withCenter: CGPoint(x: rect.minX + smallCornerRadius, y: rect.maxY - smallCornerRadius), radius: smallCornerRadius,
                            startAngle: .pi / 2, // straight down
                            endAngle: .pi, // straight left
                            clockwise: true)
                
                // draw left line
                path.addLine(to: CGPoint(x: rect.minX, y: rect.minY + smallCornerRadius))
                
                // draw top left corner
                path.addArc(withCenter: CGPoint(x: rect.minX + smallCornerRadius, y: rect.minY + smallCornerRadius), radius: smallCornerRadius,
                            startAngle: .pi, // straight left
                            endAngle: .pi / 2 * 3, // straight up
                            clockwise: true)
                
                // draw top line
                path.addLine(to: CGPoint(x: rect.maxX - largeCornerRadius, y: rect.minY))
                
                // draw concave top right corner
                // first arc
                path.addArc(withCenter: CGPoint(x: rect.maxX + largeCornerRadius, y: rect.minY + upperCornerSpacerRadius), radius: upperCornerSpacerRadius, startAngle: .pi / 2 * 3, // straight up
                            endAngle: .pi / 2, // straight left
                            clockwise: true)
                
                // second arc
                path.addArc(withCenter: CGPoint(x: rect.maxX + largeCornerRadius + imageToArcSpace, y: rect.minY + largeCornerRadius + upperCornerSpacerRadius * 2 + imageToArcSpace), radius: largeCornerRadius + imageToArcSpace, startAngle: CGFloat(240.0).toRadians(), // up with offset
                            endAngle: .pi, // straight left
                            clockwise: false)
                
                // draw right line
                path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY - smallCornerRadius))
                
                // draw bottom right corner
                path.addArc(withCenter: CGPoint(x: rect.maxX - smallCornerRadius, y: rect.maxY - smallCornerRadius), radius: smallCornerRadius,
                            startAngle: 0, // straight right
                            endAngle: .pi / 2, // straight down
                            clockwise: true)
                
                // draw bottom line to close the shape
                path.close()
                
                shapeLayer.path = path.cgPath
            }
        }
        
        extension CGFloat {
            func toRadians() -> CGFloat {
                return self * CGFloat(Double.pi) / 180.0
            }
        }
        
        class RoundImageView: UIImageView {
            override func layoutSubviews() {
                layer.masksToBounds = true
                layer.cornerRadius = bounds.size.height * 0.5
            }
        }
        
        class GrayGradientView: UIView {
            private var gradLayer: CAGradientLayer!
            
            override class var layerClass: AnyClass {
                return CAGradientLayer.self
            }
            override init(frame: CGRect) {
                super.init(frame: frame)
                commonInit()
            }
            required init?(coder: NSCoder) {
                super.init(coder: coder)
                commonInit()
            }
            func commonInit() -> Void {
                
                let myColors: [UIColor] = [
                    UIColor(white: 0.95, alpha: 1.0),
                    UIColor(white: 0.90, alpha: 1.0),
                ]
                
                gradLayer = self.layer as? CAGradientLayer
                
                // assign the colors (we're using map to convert UIColors to CGColors
                gradLayer.colors = myColors.map({$0.cgColor})
                
                // start at the top
                gradLayer.startPoint = CGPoint(x: 0.25, y: 0.0)
                
                // end at the bottom
                gradLayer.endPoint = CGPoint(x: 0.75, y: 1.0)
                
            }
        }
        

        How to initialize an empty object of a custom UIImageView subclass

        copy iconCopydownload iconDownload
        class CustomImageView: UIImageView {
            //...
            convenience init() {
                self.init(frame: .zero) // or self.init(image: nil)
            }
        }
        

        Update SwiftUI View size to match image

        copy iconCopydownload iconDownload
        ScrollView(...) {
            HStack(...) {
                ForEach(...) { ...
                    ItemCell()
                        .fixedSize()
                }
            }
        }
        
        struct ItemCell: View {
            var body: some View {
                VStack {
                    Spacer()
                    CustomImageView(from: imageURL, placeholder: PlaceholderView(), config: { $0.resizable() })
                        .aspectRatio(contentMode: .fit)
                        .frame(maxWidth: 150, idealHeight: 190)
                }
        
            }
        }
        
        ScrollView(...) {
            HStack(...) {
                ForEach(...) { ...
                    ItemCell()
                        .fixedSize()
                }
            }
        }
        
        struct ItemCell: View {
            var body: some View {
                VStack {
                    Spacer()
                    CustomImageView(from: imageURL, placeholder: PlaceholderView(), config: { $0.resizable() })
                        .aspectRatio(contentMode: .fit)
                        .frame(maxWidth: 150, idealHeight: 190)
                }
        
            }
        }
        

        dynamic image resizing after asynchronously downloading it from the firebase

        copy iconCopydownload iconDownload
        class viewControllerHomeCell: UITableViewCell  { 
        
            @IBOutlet var mainImage: UIImageView!
            @IBOutlet var viewImageViewHeight: NSLayoutConstraint!
         }
        
        func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
         {
             let cellData =  TableViewControllerHome.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! viewControllerHomeCell
        
                cellData.mainImage.sd_setImage(with:URL.init(string: "Pass Yor image Url string here"))
                { (image, error, imageCacheType, url) in
        
                     cellData.mainImage.image = image
        
                     viewImageViewHeight.constant =  image?.size.height
                }
        
                cellData.mainImage.loadImagesWithUrl(from: self.postArray[indexPath.row].covrImgUrl)
        }
        
        class viewControllerHomeCell: UITableViewCell  { 
        
            @IBOutlet var mainImage: UIImageView!
            @IBOutlet var viewImageViewHeight: NSLayoutConstraint!
         }
        
        func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
         {
             let cellData =  TableViewControllerHome.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! viewControllerHomeCell
        
                cellData.mainImage.sd_setImage(with:URL.init(string: "Pass Yor image Url string here"))
                { (image, error, imageCacheType, url) in
        
                     cellData.mainImage.image = image
        
                     viewImageViewHeight.constant =  image?.size.height
                }
        
                cellData.mainImage.loadImagesWithUrl(from: self.postArray[indexPath.row].covrImgUrl)
        }
        

        How can I use my custom image view with DataBinding in Android?

        copy iconCopydownload iconDownload
        override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        holder.binding.apply {
        
            this.image = loadImage(UiUtil.getCustomUrl(photos[position].urls.fullImage, height, width))
        
            root.setOnClickListener {
                handleClick(position)
            }
        }
        

        Community Discussions

        Trending Discussions on CustomImageView
        • Save photo SwiftUI
        • Dismiss modal automatically after 1 sec of being shown - swiftui
        • Problems sub-classing a custom ImageView
        • NSImageView 72dpi image blurry on HiDPI display
        • String Boundingrect calculation issue when string contains '\n'
        • How to initialize an empty object of a custom UIImageView subclass
        • Update SwiftUI View size to match image
        • dynamic image resizing after asynchronously downloading it from the firebase
        • How can I use my custom image view with DataBinding in Android?
        Trending Discussions on CustomImageView

        QUESTION

        Save photo SwiftUI

        Asked 2022-Jan-18 at 20:24

        Here I get the image from the link

        struct CustomImageView: View {
            var urlString: String
            @ObservedObject var imageLoader = ImageLoaderService()
            @State var image: UIImage = UIImage()
            
            var body: some View {
                Image(uiImage: image)
                    .resizable()
                    .aspectRatio(contentMode: .fit)
                    .frame(width:100, height:100)
                    .onReceive(imageLoader.$image) { image in
                        self.image = image
                    }
                    .onAppear {
                        imageLoader.loadImage(for: urlString)
                    }
            }
        }
        
        class ImageLoaderService: ObservableObject {
            @Published var image: UIImage = UIImage()
            
            func loadImage(for urlString: String) {
                guard let url = URL(string: urlString) else { return }
                
                let task = URLSession.shared.dataTask(with: url) { data, response, error in
                    guard let data = data else { return }
                    DispatchQueue.main.async {
                        self.image = UIImage(data: data) ?? UIImage()
                    }
                }
                task.resume()
            }
            
        }
        

        Here I display the received photo by the link and try to save it via .contextMenu

          VStack {
        
                    CustomImageView(urlString: "https://stackoverflow.design/assets/img/logos/so/logo-stackoverflow.png")
                        .contextMenu {
                            
                          Button(action: {
         
                            }) {
                                HStack {
                                    
                                    Text("Save image")
                                    Image(systemName: "square.and.arrow.down.fill")
                                    
                                }
                            }
                        }
                    }
        

        When you click on a photo, a .contextMenu opens with a save button, but the photo is not saved, what should I do?

        ANSWER

        Answered 2022-Jan-18 at 20:24

        in your button action add

        UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil)
        

        where image is a UIImage, so you need to pass the image from your model and pass it to the above snippet.

        Full Code:

        struct CustomImageView: View {
            var urlString: String
            @ObservedObject var imageLoader: ImageLoaderService
            @State var image: UIImage = UIImage()
            var body: some View {
                Image(uiImage: image)
                    .resizable()
                    .aspectRatio(contentMode: .fit)
                    .frame(width:100, height:100)
                    .onReceive(imageLoader.$image) { image in
                        self.image = image
                    }.onAppear {
                        imageLoader.loadImage(for: urlString)
                    }
            }
        }
        
         class ImageLoaderService: ObservableObject {
            @Published var image: UIImage = UIImage()
            func loadImage(for urlString: String) {
                guard let url = URL(string: urlString) else {return}
                let task = URLSession.shared.dataTask(with: url) { data, response, error in
                    guard let data = data else {return}
                    DispatchQueue.main.async {
                        self.image = UIImage(data: data) ?? UIImage()
                    }
                }
                task.resume()
            }
        }
        
        struct ContentView: View {
            @ObservedObject var imageLoader = ImageLoaderService()
            var body: some View {
                VStack {
                    CustomImageView(urlString: "https://stackoverflow.design/assets/img/logos/so/logo-stackoverflow.png", imageLoader: imageLoader)
                        .contextMenu {
                            Button(action: {
                                UIImageWriteToSavedPhotosAlbum(imageLoader.image, nil, nil, nil)
                            }) {
                                HStack {
                                    Text("Save image")
                                    Image(systemName: "square.and.arrow.down.fill")
        
                                }
                            }
                        }
                }
            }
        }
        

        Source https://stackoverflow.com/questions/70760512

        Community Discussions, Code Snippets contain sources that include Stack Exchange Network

        Vulnerabilities

        No vulnerabilities reported

        Install CustomImageView

        You can download it from GitHub.
        You can use CustomImageView like any standard Java library. Please include the the jar files in your classpath. You can also use any IDE and you can run and debug the CustomImageView component as you would do with any other Java program. Best practice is to use a build tool that supports dependency management such as Maven or Gradle. For Maven installation, please refer maven.apache.org. For Gradle installation, please refer gradle.org .

        Support

        For any new features, suggestions and bugs create an issue on GitHub. If you have any questions check and ask questions on community page Stack Overflow .

        DOWNLOAD this Library from

        Find, review, and download reusable Libraries, Code Snippets, Cloud APIs from
        over 430 million Knowledge Items
        Find more libraries
        Reuse Solution Kits and Libraries Curated by Popular Use Cases
        Explore Kits

        Save this library and start creating your kit

        Explore Related Topics

        Share this Page

        share link
        Consider Popular Android Libraries
        Try Top Libraries by SeptiyanAndika
        Compare Android Libraries with Highest Support
        Compare Android Libraries with Highest Quality
        Compare Android Libraries with Highest Security
        Compare Android Libraries with Permissive License
        Compare Android Libraries with Highest Reuse
        Find, review, and download reusable Libraries, Code Snippets, Cloud APIs from
        over 430 million Knowledge Items
        Find more libraries
        Reuse Solution Kits and Libraries Curated by Popular Use Cases
        Explore Kits

        Save this library and start creating your kit

        • © 2022 Open Weaver Inc.