In Swift, how to reuse a property in a UIVIewController - ios

The question is very simple and I bet lots of you guys may had this situation.
I just have a property name called "carrotImage" like a ">" symbol as a property and want to place 2 times in my UIViewController by using view.addSubview. I don't actually want to create another new property which has exactly the same image as ">", but I'm thinking that if I really do view.addSubview(carrotImage) 2 times but in different places, and apply SnapKit or built-in autolayouts on them respectively, it would affect the other one.
As you can see the image:
There are 2 '>' and I really don't want to create 2 exactly same thing. is there a good way to do it without repeating? Thanks.
Here is my carrotImage property:
var carrotImage: UIImageView = {
let imageView = UIImageView()
imageView.image = UIImage(systemName: "arrowtriangle.right.fill")
imageView.tintColor = .lightGray
return imageView
}()

For your use case, you do have to have 2 separate UIImageViews. However, they have the same image and properties, so you can reuse the code. If you do not need to later reference carrotImage, you can just make it computed variable like this (remove = and ()):
var carrotImage: UIImageView {
let imageView = UIImageView()
imageView.image = UIImage(systemName: "arrowtriangle.right.fill")
imageView.tintColor = .lightGray
return imageView
}
This way you can reuse the same code to create new UIImageView with arrowtriangle.right.fill where you need.

Related

SF Symbol imageView does not conform to pointSize?

I have an empty UIImageView in my storyboard, and have the outlet in my ViewController.swift file. In viewDidLoad, I'm calling the below code:
let config = UIImage.SymbolConfiguration(pointSize: 50, weight: .light)
self.symbol.image = UIImage(systemName: "calendar")
self.symbol.preferredSymbolConfiguration = config
The symbol loads fine, and even the weight is correctly reflected.
However, the size of the symbol image is still tied to the size of the UIImageView in the storyboard even though I have no constraints set on the UIImageView. And from what I can tell the pointSize declared in SymbolConfiguration has no effect on the size of the symbol image.
From what I can tell it seems that there are two ways to get pointSize in SF Symbols to work: 1. Do all of it programmatically or 2. do all of it in Storyboard / Inspector.
Does anyone know what I'm doing wrong or does SF Symbols not support Storyboard + code configuration?
I solved the problem by changing the contentMode property of the UIImageView object to .center.
let imageView = UIImageView()
imageView.contentModel = .center // This is the key step.
imageView.preferredSymbolConfiguration = .init(pointSize: 10)
iconView.image = UIImage(systemName: "name")
I figured it out, you have to call sizeToFit() after setting the configuration. I'm guessing Apple left it for the dev to call it so that they could prevent unnecessary recalculation of UI sizing thus potentially improving efficiency.
Anyway, the solution is: call sizeToFit() on the UIImageView after setting the configuration.
Try this way instead:
let config = UIImage.SymbolConfiguration(pointSize: 50, weight: .light)
self.symbol.image = UIImage(systemName: "calendar", withConfiguration: config)

TableView Cell with Minor Lag (Async Proper Use?) Swift

I have an array full of Data loading images in a tableView Cell. However I am getting minor lag(more of a glitch) when the table view scrolls on image index.
Array Contains Data(Seems to lag more bigger the bytes)
Data in Bytes(624230 bytes)
Data in Bytes(1619677 bytes)
Data in Bytes(2257181 bytes)
Data in Bytes(1120275bytes)
Not Sure How to properly use Async When the data is loaded.
struct messageCellStruct {
let message: String!
let photoData: Data!
}
var messageArray = [messageCellStruct]()
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell")
let message2 = cell?.viewWithTag(2) as! UITextView
var photoUploadData = messageArray[indexPath.row].photoData
let main = DispatchQueue.main
let background = DispatchQueue.global()
let helper = DispatchQueue(label: "another_thread")
if(photoUploadData != nil){
print("Data in Bytes\(String(describing: photoUploadData))")
let fullString = NSMutableAttributedString(string: "")
let image1Attachment = NSTextAttachment()
let newImageWidth = (self.message.bounds.size.width - 20 )
let messageDisplayString = self.messageArray[indexPath.row].message
background.async {
image1Attachment.image = UIImage(data: photoUploadData!)
}
image1Attachment.bounds = CGRect.init(x: 0, y: 0, width: newImageWidth, height: 200)
let image1String = NSAttributedString(attachment: image1Attachment)
fullString.append(image1String)
fullString.append(NSAttributedString(string: messageDisplayString!))
message2.attributedText = fullString
message2.textColor = .white
message2.font = UIFont.systemFont(ofSize: 17.0)
}else {
message2.text = self.messageArray[indexPath.row].message
}
return cell!
}
The reason I introduced the Async in the first place was because of the lag. It lags with and without the Async.
You should never perform any UI operations on background thread.
remove background.async from
background.async {
image1Attachment.image = UIImage(data: photoUploadData!)
}
simply use
image1Attachment.image = UIImage(data: photoUploadData!)
EDIT:
The lag is not because of UIImage(data: photoUploadData!) I agree that it is a synchronous call but that wont create a lag the real culprit is let image1String = NSAttributedString(attachment: image1Attachment) NSAttributedString is known to be notorious.
To test the hypothesis you can comment out
let image1String = NSAttributedString(attachment: image1Attachment)
fullString.append(image1String)
fullString.append(NSAttributedString(string: messageDisplayString!))
message2.attributedText = fullString
message2.textColor = .white
message2.font = UIFont.systemFont(ofSize: 17.0)
and you should not see a lag.
Unfortunately I don't know the way to fix the issue with NSAttributedString we had same issue and it would often add up lag on scrolling huge pile of rows. Hence we decided to opt for DTCoreText
Somehow this performs better than NSAttributedString
EDIT:
We concluded that the delay/lag is probably because of conversion of huge data to NSAttributedString. As all that OP wanna do is show image and text below it n he did not know how to handle multiple components in cell I am updating the answer to same.
I am not claiming that this is the only way to do it this way might help the OP is assumption here
Step1:
Create UITableViewCell xib and drag UIImageView to it.
Now imageViews can take implicit size. What does that mean is UIImageView's can grow based on the image shows. If the images you are loading happen to be in your control (Server sending images is yours) and if your backend team can assure that they wont send crazy big images you dont need height constraint to ImageView.
But more often than not, server team claims that its a client team job. Because you would like to show the image best possible way and showing image with aspect fit and let the bigger part of image being chopped off or if image happens to be small leaving the huge space around image ask the backend team to send aspect ratio as a part of the response.
So in that case create a height constraint to imageView
Create an IBOutlet to the height constraint lets assume you call it as imageHeightConstraint
Now when you load the image in cell, you know that imageView's width will be equal to the width of the cell and you know the aspect ratio of the image to be shown so you can calculate the height of supposed image as
either in cellFroRowAtIndexPath
cell.imageHeightConstraint.constant = cell.bounds.size.width * aspectRatioOfImage
or better if you have configure method in a cell where you would expect cell to configure its subviews then
self.imageHeightConstraint.constant = self.bounds.size.width * aspectRatioOfImage
Obviously you might need to convert it to Float from CGFloat am sure u can do it :)
Now if you dont have any control over image and you are downloading it from some random website and hence dont have aspect ratio info then rather than adding height constraint add aspect ratio constraint to imageView and change imageView content mode to .aspectFit this might have side effects I mentioned above but thats the best you get without any support :)
Now add TextView below imageView
add vertical height constraint between imageView and TextView
Select textView and set scroll enabled as false
Select TextView and change vertical content hugging priority and compression resistance to 999
Thats it :)
Now what have you achieved with all these circus is that now you have a TableViewCell which can take implicit size based on content it has without any ambiguity :)
Now that makes the life easy. Implement tableView delegates and dont forget to use
self.tableView.rowHeight = UITableViewAutomaticDimension;
self.tableView.estimatedRowHeight = 44.0;
Thats it :) Now run your code and enjoy self expanding tableView cells based on the content they show :) And you have the layout u wanted
Hope it helps

Swift 3 - Access nested subviews properties in code

For a pure matter of training I'm developing a weather app coding the entire UI rather than using storyboard.
I have a nested structure of views as follows:
SuperView --> UIView (with 5 subviews of type UIView).
Each of the 5 UIViews contains: 1 UIImageView, 2 UILabels
Now, when I'm calling my delegate function to retrieve the weather I'm having trouble updating those values with weather icon, weather description, day.
I tried using Tags for each of the subviews but no joy.
To give you something to look at:
This is where I retrieve my forecast data (icons, description, day):
//MARK: Forecast Wheel elements
let forecastWeatherWheel = UIView()
var forecastDays = [String]()
var forecastDescriptions = [String]()
var forecastIcons = [String]()
func setForecastWeather(forecast: ForecastWeatherData) {
forecastDays = forecast.forecastDay
forecastDescriptions = forecast.weatherDescription
forecastIcons = forecast.icon
for (index,forecastContainerView) in (forecastWeatherWheel.subviews.filter{$0 is UIView}).enumerated(){
for (index,iconImageView) in (forecastContainerView.subviews.filter{$0 is UIImageView}).enumerated(){
let iconImage = iconImageView as! UIImageView
iconImage.image = UIImage(imageLiteralResourceName: forecastIcons[index])
}
}
}
With that nested for I've been - somehow - able to access the image property of my nested view but rather than looping through the array of icons it's using always the same Icon in all the 5 subviews...
Any help is highly appreciated as I'm struggling with this since more than 12 hrs :|
The real answer is of course to use a view subclass, with accessors for the image view and each label, instead of using the subview hierarchy like this. But here's what's wrong with what you're doing right now:
for (index,forecastContainerView) in (forecastWeatherWheel.subviews.filter{$0 is UIView}).enumerated(){
The filter here is pointless; everything in subviews is a UIView. You'll get 5 passes through here.
for (index,iconImageView) in (forecastContainerView.subviews.filter{$0 is UIImageView}).enumerated(){
Your filter here is only going to return a single view - the image view, since the others aren't image views. That means this loop is only going to execute once.
let iconImage = iconImageView as! UIImageView
iconImage.image = UIImage(imageLiteralResourceName: forecastIcons[index])
Which means that index here is your inner index, which is always 0.
Either use a different name for each index variable, or write it something like this (untested, typed in browser):
for (index, forecastContainerView) in forecastWeatherWheel.subviews.enumerated() {
let imageView = forecastContainerView.subviews.first(where: { $0 is UIImageView } ) as! UIImageView
imageView.image = UIImage(imageLiteralResourceName: forecastIcons[index]
}

Keep aspect ratio for UIbuttons background image

I've been looking for hours and all I find is old answers saying that it cannot be done.
I have a button where I'd like the background image to be as large as it can be, while keeping its aspect ratio. The background image keeps getting streched no matter what I do. I've tried.
var bluecircle = #imageLiteral(resourceName: "BLUE.png")
monBreak.contentMode = UIViewContentMode.scaleAspectFit
monBreak.setBackgroundImage(bluecircle, for: UIControlState.normal)
Do I really need to make an imageview and put an invisible button over it? That seems like welcoming A LOT of new things that could go wrong.
I tried
#IBOutlet weak var monBreak: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
var bluecircle = #imageLiteral(resourceName: "BLUE.png")
let theimagebehindmonBreak = UIImageView(image: bluecircle)
theimagebehindmonBreak.frame = CGRectFromString( "{{0,0},{40,40}}")
monBreak.contentMode = UIViewContentMode.scaleAspectFit
monBreak.addSubview(theimagebehindmonBreak)
theimagebehindmonBreak.image=bluecircle
theimagebehindmonBreak.contentMode = UIViewContentMode.scaleAspectFit
This gets the aspect ratio right, but since I want the layout to be dynamic setting its size to fixed values won't work, plus it's not centered.
At first I tired simply applying the constraints of the button like so
var dacontraints = NSLayoutConstraint(monBreak.constraints)
But at this point I get the error "Cannot invoke initializer for type 'NSLayoutConstraint' with an argument list of type '{[NSLayoutconstraints]}'
This is killing me
Sounds like a solution where subclassing fits.
Subclass UIButton, add an ImageView in the init, size it to the frame, and set the contentMode properly.
Old question but since I was looking for the same I think this is still valid.
I was trying to do the same (as everyone says) for backgroundImage it is impossible. However, if your button will consist of only an icon, then first you should be using the setImage method AND if you are using it you'll have access to the imageView property of the button. Since this is an UIImageView you can set the contentMode to whatever you like.

Set contentMode of UIImageView

In Obj-C
imageView.contentMode = UIViewContentModeScaleAspectFill;
would set the contentMode.
Why does
imageView.contentMode = UIViewContentModeScaleAspectFill
not work in Swift?
Somewhat confusingly, Swift drops the prefix for ObjC enum values:
imageView.contentMode = .scaleAspectFill
This is because Swift already knows what enum type is being used. Alternatively, you can specify the enum too:
imageView.contentMode = UIViewContentMode.scaleAspectFill
Note: In versions of Swift before version 3, "scaleAspectFill" will need to be capitalized.
In swift language we can set content mode of UIImage view like
var newImgThumb : UIImageView
newImgThumb = UIImageView(frame:CGRectMake(0, 0, 100, 70))
newImgThumb.contentMode = .ScaleAspectFit
tldr;
See the code answer for Swift 3 at the bottom.
Note - Please comment if more information is needed.
Please checkout the longer answer below which includes how to use the solution for setting all other properties within your Storyboard or Xib/Nib file.
There's nothing wrong with the other answers but I want to share how setting values on objects can be done in Interface Builder. I know the OP is asking for code, this example is just being shared for completeness. Of course if one wanted to animate property changes or needed the code versions then the other answers continue to apply.
Within Interface Builder
Select the ImageView (or any other control that has an embedded
imageView)
Check the base type of the attribute you want to set (in the case of contentMode this is UIViewContentMode)* see NB for how to...
Note the type of valid values that can be assigned to the base type (in this
case contentMode values correspond to a number)
Go to the attributes inspector and see the User Defined Runtime Attributes (see image)
Add a user defined attribute of type
Number and the property name that you want to set (in this case it
would be contentMode)
NB - An easy way to explore the underlying types of properties is to Cmd+Click on the property in your source code editor, then Cmd+Click on the type for that property.
Here is a simple example where I set some properties for a UIButton, which includes a UIImageView as one of its subviews. The example shows how one can set properties on the top object (UIButton) and the sub object (UIImageView).
If you have an imageView selected then just set the User Defined Runtime Attribute to contentMode of type Number and whatever value you want. This is a good method because it will work for both Objc and Swift.
What's great is that you can use the same method to capture many other static property values for anything that appears within Interface Builder.
Documented values for the UIViewContentMode enum
BTW - Swift 3 changes the enum values to begin with a lower case so the following would work in Swift 3:
imageView.contentMode = .scaleAspectFill
In Swift 4 it is
imageView.contentMode = UIView.ContentMode.scaleAspectFit
In Swift Language we can set imageView in textField as given below.
let RightImageView = UIImageView()
RightImageView.image = image
let RightView = UIView()
RightView.addSubview(RightImageView)
RightView.frame = CGRectMake(0, 0, 30,30)
Give color to view & imageView So that you can check your added imageView position in textField
RightView.backgroundColor = UIColor.redColor()
RightImageView.backgroundColor = UIColor.blueColor()
RightImageView.contentMode = UIViewContentMode.ScaleAspectFill
RightImageView.frame = CGRectMake(0, 0,30,30)
textFieldForCountry.rightView = RightView

Resources