I am trying to create a custom cell in Eureka that display an Image. When tap the image, the image displays full screen with black background.
Usually you do it with view.addSubview(newImageView), but it does not seem like Eureka cell has view class.
Here is what I got so far for the cell:
final class ImageViewCell: Cell<ImageView>, CellType {
#IBOutlet weak var viewImage: UIImageView!
//let storage = Storage.storage().reference()
required init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func setup() {
super.setup()
//cell not selectable
selectionStyle = .none
viewImage.contentMode = .scaleAspectFill
viewImage.clipsToBounds = true
height = {return 300 }
//make userImage reconize tapping
let tapRec = UITapGestureRecognizer(target: self, action: #selector(imageTapHandler(tapGestureRecognizer:)))
viewImage.addGestureRecognizer(tapRec)
viewImage.isUserInteractionEnabled = true
}
#objc func imageTapHandler(tapGestureRecognizer: UITapGestureRecognizer) {
let imageView = tapGestureRecognizer.view as! UIImageView
let newImageView = UIImageView(image: imageView.image)
newImageView.frame = UIScreen.main.bounds
newImageView.backgroundColor = .black
newImageView.contentMode = .scaleAspectFit
newImageView.isUserInteractionEnabled = true
let tap = UITapGestureRecognizer(target: self, action: #selector(dismissFullscreenImage))
newImageView.addGestureRecognizer(tap)
view.addSubview(newImageView)
}
#objc func dismissFullscreenImage(_ sender: UITapGestureRecognizer) {
sender.view?.removeFromSuperview()
}
override func update() {
super.update()
// we do not want to show the default UITableViewCell's textLabel
textLabel?.text = nil
// get the value from our row
guard let imageData = row.value else { return }
// get user image data from value
let downloadData = imageData.pictureData
viewImage.image = UIImage(data: downloadData)
}
}
I am getting "Use of unresolved identifier 'view'" when trying to addSubview.
I have tried to use contentView instead of view, and the result is like this:
Screenshot for the View
If you want to show the image in fullScreen, cell's contentView or the viewController subView that holds this cell are not the right places to add this image as subview.
A proper solution to this is to use the onCellSelection(something as shown below) callback in your container ViewController and present a new ViewController that display's only image fullscreen or whatever customization you want.
imageCell.onCellSelection({[weak self] (cell, row) in
let imageVC = DisplayViewController()
imageVC.image = cell.viewImage.image
self?.present(imageVC, animated: true, completion: nil)
})
If you want to show fullscreen only when user taps the image in cell then you should get the tapGesture callback in container ViewController and show the image as above.
Thanks for Kamran, I think I found the solution.
for Eureka rows, you can always use row.onCellSelection call back.
in my case I can do this when calling the custom row:
<<< ImageViewCellRow(){ row in
row.value = ImageView(pictureData: data!)
row.onCellSelection(showMe)
}
then create a showMe function like this:
func showMe(cell: ImageViewCell, row: (ImageViewCellRow)) {
print("tapped my ImageViewCell!")
}
now you should be able to present a full screen image as Kamran's code.
Related
I have a list of images displaying in a UICollectionViewCell. Now I want a way to display an overlay on the image when user long press on the cell. I have been able to place a long press gesture on the cell but unfortunately how to perform the overlay on the cell is where I'm struggling to achieve.
In my cellForItemAt I have this
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reUseMyCellID, for: indexPath) as! MyCollectionCell
cell.gestureRecognizers?.removeAll()
cell.tag = indexPath.row
let directFullPreviewer = UILongPressGestureRecognizer(target: MyCollectionCell(), action: #selector(MyCollectionCell().directFullPreviewLongPressAction))
cell.addGestureRecognizer(directFullPreviewer)
I have this function for the action on LongPressGestureRecognizer in my MyCollectionCell
class MyCollectionCell: UICollectionViewCell {
weak var textLabel: UILabel!
let movieImage: UIImageView = {
let image = UIImageView()
image.translatesAutoresizingMaskIntoConstraints = false
image.clipsToBounds = true
image.contentMode = .scaleAspectFill
image.layer.cornerRadius = 10
// image.image = UIImage(named: "105")
return image
}()
let movieOverlay: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = .black.withAlphaComponent(0.7)
view.alpha = 0
return view
}()
override init(frame: CGRect) {
super.init(frame: frame)
contentView.addSubview(movieImage)
movieImage.addSubview(btnRate)
movieImage.addSubview(movieOverlay)
NSLayoutConstraint.activate([
movieImage.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 10),
movieImage.topAnchor.constraint(equalTo: contentView.topAnchor),
movieImage.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -10),
movieImage.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
movieOverlay.leadingAnchor.constraint(equalTo: movieImage.leadingAnchor),
movieOverlay.topAnchor.constraint(equalTo: movieImage.topAnchor),
movieOverlay.trailingAnchor.constraint(equalTo: movieImage.trailingAnchor),
movieOverlay.bottomAnchor.constraint(equalTo: movieImage.bottomAnchor)
])
}
override func prepareForReuse() {
super.prepareForReuse()
movieImage.image = nil
}
func configure(with urlString: String){
movieImage.sd_setImage(with: URL(string: urlString), placeholderImage: UIImage(named: "ImagePlaceholder"))
}
#objc func directFullPreviewLongPressAction(g: UILongPressGestureRecognizer)
{
if g.state == UIGestureRecognizer.State.began
{
movieOverlay.alpha = 1
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Your current code has many issues when creating the long press gesture.
You are setting the target to a new cell instance that is immediately thrown away. Set the target as cell, not MyCollectionCell().
You are also using the wrong syntax for the selector. Don't attempt to create a new instance of a cell. Just pass the name of the method #selector(MyCollectionCell.directFullPreviewLongPressAction).
Having said all of that, there is no reason this code should be in the cellForItemAt method. You should be creating the long press gesture inside the cell class.
Remove these three lines from cellForItemAt:
cell.gestureRecognizers?.removeAll()
let directFullPreviewer = UILongPressGestureRecognizer(target: MyCollectionCell(), action: #selector(MyCollectionCell().directFullPreviewLongPressAction))
cell.addGestureRecognizer(directFullPreviewer)
Then add the following lines inside the init of MyCollectionCell:
let directFullPreviewer = UILongPressGestureRecognizer(target: self, action: #selector(directFullPreviewLongPressAction))
addGestureRecognizer(directFullPreviewer)
Now the cell is fully responsible for setting up and handling the long press gesture.
Unrelated to your question, you should know that the line:
cell.tag = indexPath.row
should be:
cell.tag = indexPath.item
row is used for UITableView. item is used for UICollectionView.
But besides that, you really should avoid such code. If your collection view allows cells to be inserted, deleted, and/or reordered, then a cell's tag will no longer represent the item you set.
Swift 5/Xcode 12.4
I created an xib file for my custom MarkerView - the layout's pretty simple:
- View
-- StackView
--- DotLabel
--- NameLabel
View and StackView are both set to "User Interaction Enabled" in the inspector by default. DotLabel and NameLabel aren't but ticking their boxes doesn't seem to actually change anything.
At runtime I create MarkerViews (for testing purposes only one atm) in my ViewController and add them to a ScrollView that already contains an image (this works):
override func viewDidAppear(_ animated: Bool) {
createMarkers()
setUpMarkers()
}
private func createMarkers() {
let marker = MarkerView()
marker.setUp("Some Text")
markers.append(marker)
scrollView.addSubview(marker)
marker.alpha = 0.0
}
private func setUpMarkers() {
for (i,m) in markers.enumerated() {
m.frame.origin = CGPoint(x: (i+1)*100,y: (i+1)*100)
m.alpha = 1.0
}
}
These MarkerViews should be clickable, so I added a UITapGestureRecognizer but the linked function is never called. This is my full MarkerView:
class MarkerView: UIView {
#IBOutlet weak var stackView: UIStackView!
#IBOutlet weak var dotLabel: UILabel!
#IBOutlet weak var nameLabel: UILabel!
let nibName = "MarkerView"
var contentView:UIView?
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() {
guard let view = loadViewFromNib() else { return }
view.frame = self.bounds
self.addSubview(view)
contentView = view
let gesture = UITapGestureRecognizer(target: self, action: #selector(self.clickedMarker))
self.addGestureRecognizer(gesture)
}
func loadViewFromNib() -> UIView? {
let bundle = Bundle(for: type(of: self))
let nib = UINib(nibName: nibName, bundle: bundle)
return nib.instantiate(withOwner: self, options: nil).first as? UIView
}
func setUp(_ name:String) {
nameLabel.text = name
nameLabel.sizeToFit()
stackView.sizeToFit()
}
#objc private func clickedMarker() {
print("Clicked Marker!")
}
}
Other questions recommend adding self.isUserInteractionEnabled = true before the gesture recognizer is added. I even enabled it for the StackView and both labels and also tried to add the gesture recognizer to the ScrollView but none of it helped.
Why is the gesture recognizer not working and how do I fix the problem?
Edit: With Sweeper's suggestion my code now looks like this:
class MarkerView: UIView, UIGestureRecognizerDelegate {
.....
func commonInit() {
guard let view = loadViewFromNib() else { return }
view.frame = self.bounds
let gesture = UITapGestureRecognizer(target: self, action: #selector(self.clickedMarker))
gesture.delegate = self
self.isUserInteractionEnabled = true
self.addGestureRecognizer(gesture)
self.addSubview(view)
contentView = view
}
.....
func gestureRecognizer(_: UIGestureRecognizer, _ otherGestureRecognizer: UIGestureRecognizer) -> Bool {
print("gestureRecognizer")
return true
}
}
"gestureRecognizer" is never printed to console.
One of the comments below the accepted answer for the question #Sweeper linked gives a good hint:
What is the size of the content view (log it)?
print(self.frame.size)
print(contentView.frame.size)
both printed (0.0, 0.0) in my app. The UITapGestureRecognizer is attached to self, so even if it worked, there was simply no area that you could tap in for it to recognize the tap.
Solution: Set the size of the UIView the UITapGestureRecognizer is attached to:
stackView.layoutIfNeeded()
stackView.sizeToFit()
self.layoutIfNeeded()
self.sizeToFit()
didn't work for me, the size was still (0.0, 0.0) afterwards. Instead I set the size of self directly:
self.frame.size = stackView.frame.size
This also sets the size of the contentView (because it's the MarkerView's child) but of course requires the size of stackView to be set properly too. You could add up the childrens' widths/heights (including spacing/margins) yourself or simply call:
stackView.layoutIfNeeded()
stackView.sizeToFit()
The finished code:
func setUp(_ name:String) {
nameLabel.text = name
//nameLabel.sizeToFit() //Not needed anymore
stackView.layoutIfNeeded() //Makes sure that the labels already use the proper size after updating the text
stackView.sizeToFit()
self.frame.size = stackView.frame.size
let gesture = UITapGestureRecognizer(target: self, action: #selector(self.onClickStackView))
self.addGestureRecognizer(gesture)
}
Specifics:
There's no need for a delegate and overriding func gestureRecognizer(_: UIGestureRecognizer, _ otherGestureRecognizer: UIGestureRecognizer) -> Bool to always return true, which might be an alternative but I didn't manage to get that version to work - the function was never called. If someone knows how to do it, please feel free to post it as an alternative solution.
Attaching the UITapGestureRecognizer has to be done once the parent view knows its size, which it doesn't in commonInit because the text of the labels and the size of everything isn't set there yet. It's possible to add more text after the recognizer is already attached but everything that exceeds the original length (at the time the recognizer was attached) won't be clickable if using the above code.
The parent view's background color is used (the stackView's is ignored) but it's possible to simply set it to a transparent color.
Attaching the gesture recognizer to stackView instead of self works the same way. You can even use a UILabel but their "User Interaction Enabled" is off by default - enable it first, either in the "Attributes Inspector" (tick box in the "View" section) or in code (myLabel.isUserInteractionEnabled = true).
So Im trying to create a UIBarButtonItem with a custom UIView by subclassing it like so.
import UIKit
import SnapKit
class LocationManager: UIBarButtonItem {
let createdView = UIView()
lazy var currentCityLabel: UILabel = {
let currentCityLabel = UILabel()
currentCityLabel.text = "Philadelphia, PA"
guard let customFont = UIFont(name: "NoirPro-SemiBold", size: 20) else {
fatalError("""
Failed to load the "CustomFont-Light" font.
Make sure the font file is included in the project and the font name is spelled correctly.
"""
)
}
currentCityLabel.adjustsFontForContentSizeCategory = true
return currentCityLabel
}()
lazy var downArrow: UIImageView = {
let downArrow = UIImageView()
downArrow.contentMode = .scaleAspectFit
downArrow.image = UIImage(named: "downArrow")
return downArrow
}()
override init() {
super.init()
setupViews()
}
#objc func setupViews(){
customView = createdView
createdView.addSubview(currentCityLabel)
currentCityLabel.snp.makeConstraints { (make) in
make.left.equalTo(createdView.snp.left)
make.top.bottom.equalTo(createdView)
}
createdView.addSubview(downArrow)
downArrow.snp.makeConstraints { (make) in
make.left.equalTo(currentCityLabel.snp.right).offset(5)
}
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
However, when I create it and assign it in my viewController I see nothing
import UIKit
class ViewController: UICollectionViewController {
override func viewDidLoad() {
super.viewDidLoad()
setupViews()
}
#objc func setupViews(){
guard let collection = collectionView else {
return
}
collection.backgroundColor = .white
let customLeftBar = LocationManager()
self.navigationController?.navigationItem.leftBarButtonItem = customLeftBar
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
I've looked at other post and none seem to quite match my situation. I'm beginning to think it is because I didn't give the UIView a frame but I am not exactly sure how I would do that in this instance if that is the case. Anyone see anything I don't that could potentially help me solve this problem. Also setting a target doesn't work I tried two different ways and none of them triggers a thing
#objc func setupBarButtonItems(){
let customLeftBar = LocationManager()
customLeftBar.action = #selector(self.leftBarPressed)
customLeftBar.target = self
customLeftBar.customView?.isUserInteractionEnabled = true
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(self.leftBarPressed))
customLeftBar.customView?.addGestureRecognizer(tapGesture)
navigationItem.leftBarButtonItem = customLeftBar
}
#objc func leftBarPressed(){
print("left bar tapped")
}
Change your adding line from
self.navigationController?.navigationItem.leftBarButtonItem = customLeftBar
to
self.navigationItem.leftBarButtonItem = customLeftBar
When add the barItem, you need to add it via navigationItem of the ViewController, not NavigationController
EDITED for add the action
Your custom UIBarButtonItem is a Custom View's BarButtonItem, so the target and selector will not working.
You can add your custom action by adding a button into your customView, and send the action via closure
Init your closure
var didSelectItem: (() -> Void)?
Add the create button code in your #objc func setupViews()
let button = UIButton(type: .custom)
createdView.addSubview(button)
button.snp.makeConstraints { (maker) in
maker.top.bottom.leading.trailing.equalTo(createdView)
}
// button.backgroundColor = UIColor.cyan // uncomment this line for understand about the barbuttonitem's frame
button.addTarget(self, action: #selector(didTap(_:)), for: .touchUpInside)
and add the function
#objc func didTap(_ button: UIButton) {
print("Did tap button")
}
In your viewController, you can get the tap action by
customLeftBar.didSelectItem = { [weak self] in
self?.leftBarPressed()
}
Unfortunately, your barbuttonitem's default frame is 30x30, so you must be set the frame for your barbuttonitem. If not, you can only catch the tap action in 30x30 area (uncomment the code for see it)
I made a view and draw text on it and i want that if any text contains link(Hyperlink) or Phone Number It would be clickable (Same Behaviour As in Text View) So how to Achieve it ?
Code For View In which i am Drawing Text :-
class DrawRectCellView: UIView {
var text: NSAttributedString?
override init(frame: CGRect) {
super.init(frame: frame)
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// Only override drawRect: if you perform custom drawing.
override func draw(_ rect: CGRect)
{
UIColor.white.setFill()
UIGraphicsGetCurrentContext()?.fill(rect)
// Drawing code
if let attributedText = text {
attributedText.draw(in: rect)
}
}
}
Code For TableCell :-
class DrawRectCell: UITableViewCell {
var cellView: DrawRectCellView?
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
// Initialization code
cellView = DrawRectCellView(frame: self.frame)
if let cell = cellView {
cell.autoresizingMask = UIViewAutoresizing(rawValue: UIViewAutoresizing.RawValue(UInt8(UIViewAutoresizing.flexibleWidth.rawValue) | UInt8(UIViewAutoresizing.flexibleHeight.rawValue)))
cell.contentMode = UIViewContentMode.redraw
}
self.contentView.addSubview(cellView!)
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setTextString(_ text: NSAttributedString) {
if let view = cellView {
view.text = text
}
}
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
i am Setting Text like = www.google.com or any phone number its showing as normal text only (Not Showing Like In textview (it makes it clickable))
First you need to detect your text contain url or numbers like this way.
let input = "This is a test with the URL https://www.hackingwithswift.com to be detected."
let detector = try! NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue)
let matches = detector.matches(in: input, options: [], range: NSRange(location: 0, length: input.utf16.count))
for match in matches {
guard let range = Range(match.range, in: input) else { continue }
let url = input[range]
print(url)
}
if you detect url after setting to the textview you need to add UITapGestureRecognizer on UITextView like this way.
let tapGesture = UITapGestureRecognizer(target: self, action: "handleTap")
textview.addGestureRecognizer(tapGesture)
func handleTap(sender: UITapGestureRecognizer) {
if sender.state == .began {
//write your code here for url tap
}
}
You can add UITapGestureRecognizer on DrawRectCellView and set the IndexPath as tag. In the selector method of UITapGestureRecognizer you can get the subviews info of the tapped contentView containing text.
To highlight certain text within the TextView:
Let's say you have your textView textView, then you can use this code to highlight URLs, phone numbers, etc.:
textView.dataDetectorTypes = [] // doesn't work if textfield isEditable is set to true
textView.linkTextAttributes = [NSForegroundColorAttributeName: UIColor.blue] // the default is blue anyway
If you want to only include some specific data types to be detected, add them to the array like so:
textView.dataDetectorTypes = [.link]
Here's a list of types:
https://developer.apple.com/documentation/uikit/uidatadetectortypes
To make an entire view tappable:
You can add a UITapGestureRecognizer to the view (or not if it does not contain a phone number or hyperlink) like so:
https://stackoverflow.com/a/28675664/7270113
This answer may not be using the Swift version you are using. If so the compiler will tell you how to change it, so it will work.
If you don't like using Selectors, I recommend using the library Closures
https://github.com/vhesener/Closures, specifically with this https://vhesener.github.io/Closures/Extensions/UITapGestureRecognizer.html
I just got finished going through the big nerd ranch iOS programming book and started my first 'project'. I'm trying to use a UIImageView as a button but my image is coming up as nil and I cannot figure out why. I'm new at this, so any help, or even just identifying any parts of this code that don't make sense is appreciated.
import UIKit
class ImageScreenViewController: UIViewController {
# IBOutlet var imageView: UIImageView!
// set up images
let picture1 = UIImage(named: "picture1")
override func viewDidLoad() {
super.viewDidLoad()
// set image on screen
imageView.image = picture1
}
// set up gesture recognizer
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(count(_:)))
self.imageView.addGestureRecognizer(tapRecognizer)
self.imageView.isUserInteractionEnabled = true
}
#objc func count(_ gestureRecognizer: UIGestureRecognizer) {
print("tapped the picture")
}
}
note, picture1 is in Assets.xcassets.
Some explanation for your question
encodeWithCoder(_ aCoder: NSCoder) {
// Serialize
}
init(coder aDecoder: NSCoder) {
// Deserialize
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
//You used
}
To add a Gesture you actually do not need to implement coder here as These are being used to store the state
Check following example
1- ImageView is being taken from storyBoard
2- myImage is being Added using code
Now , Storyboard initially implants serialise and deserialise mechanism
Thus imageView is a property being added from storyboard so no need for coder there
import UIKit
class ImageVC: UIViewController {
# IBOutlet var imageView: UIImageView!
// set up images
let picture1 = UIImage(named: "cc")
//ImageView programmaticlly
var myImage = UIImageView()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
// set image on screen
imageView.image = picture1
//setting properties and frame
myImage.image = picture1
myImage.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
self.view.addSubview(myImage)
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(count(_:)))
self.imageView.addGestureRecognizer(tapRecognizer)
self.imageView.isUserInteractionEnabled = true
}
// set up gesture recognizer
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
//adding gesture
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(count(_:)))
self.myImage.addGestureRecognizer(tapRecognizer)
self.myImage.isUserInteractionEnabled = true
}
#objc func count(_ gestureRecognizer: UIGestureRecognizer) {
print("tapped the picture")
}
}
As if you implement above Algo It won't crash , as For Properties being added from storyboard can directly be accessed in didload or willAppear as we never going to remove them from superview so indirectly its serialised
Second case - myImage - Added Programmatically can also be removed from superView and added again Thus can be serialised means need to be
you can add tapRecongnize in viewDidload because in init method imageview not initialize and return nil so that you can use like this
override func viewDidLoad() {
super.viewDidLoad()
imageView.image = picture1
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(count(_:)))
self.imageView.addGestureRecognizer(tapRecognizer)
self.imageView.isUserInteractionEnabled = true
}
var image: UIImage?
#IBOutlet weak var imageChoisie: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
imageChoisie.image = image // from Segue
// Click on Image
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(count(_:)))
self.imageChoisie.addGestureRecognizer(tapRecognizer)
self.imageChoisie.isUserInteractionEnabled = true
}
#objc func count(_ gestureRecognizer: UIGestureRecognizer) {
//print("tapped the picture")
dismiss(animated: true, completion: nil) // back previous screen
}