I want to add and remove views at runtime in a stack view but am not able to do it.
Basically the situation is I am using a Button made from a Xib and adding it to a StackView, Stack View Distribution is fill equally and alignment is fill but the problem is that the button is not resizing according to the stack view.
I mean if I give initialiser a frame rect .zero then it's invisible, If I give it a CGRect(0,0,40,40) then it's a 40x40 square but don't resizes properly.
The main confusion and problem is it looks as expected in View Hierarchy Debugger but results on simulator don't translates in the same way attached some screenshots for reference and I also added some blank UIView and they are working as expected.
Code for rendering in ViewController
func render(viewModel: ViewModel) {
let rentalButton = MyCustomButton(frame: .zero)
let shiftingButton = MyCustomButton(frame: .zero)
rentalButton.titleText = "Rental button"
shiftingButton.titleText = "Shifting button"
rentalButton.subtitleText = "subtitle rental"
shiftingButton.subtitleText = "subtitle shifting"
for view in stackView.arrangedSubviews {
stackView.removeArrangedSubview(view)
view.removeFromSuperview()
}
let black = UIView()
black.backgroundColor = .black
let red = UIView()
red.backgroundColor = .red
stackView.addArrangedSubview(shiftingButton)
stackView.addArrangedSubview(black)
stackView.addArrangedSubview(rentalButton)
stackView.addArrangedSubview(red)
if viewModel.showRental && !viewModel.showHouseShifting {
rentalButton.showSubtitle = true
shiftingButton.showSubtitle = false
stackView.removeArrangedSubview(shiftingButton)
shiftingButton.removeFromSuperview()
}
else if !viewModel.showRental && viewModel.showHouseShifting {
rentalButton.showSubtitle = false
shiftingButton.showSubtitle = true
stackView.removeArrangedSubview(rentalButton)
rentalButton.removeFromSuperview()
}
stackView.layoutIfNeeded()
rentalButton.layoutIfNeeded()
}
Init Code for my Button from Xib
override init(frame: CGRect) {
super.init(frame: frame)
self._init()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
self._init()
}
private func _init() {
Bundle.main.loadNibNamed("MyCustomButton", owner: self, options: nil)
self.addSubview(self.contentView)
self.contentView.frame = self.bounds
self.contentView.autoresizingMask = [.flexibleHeight, .flexibleWidth]
setTitle(nil, for: .normal)
self.matchConstraints(toView: contentView)
decorate()
}
Related
I'm trying to display a list of recepcies for at pet proyect, which I've modeled with a UICollectionView and for each item, a custom UICollectionViewCell.
This UICollectionViewCell has inside a UIImageView and a custom UIView for layout pruposes.
The tree goes like this
HomeViewController (UIViewController)
└── UICollectionView
└── RecepieCardCollectionViewCell (UICollectionViewCell)
├── UIImageView
└── RecepieCardInfoView (UIView)
└── UILabel
I'm comforming to the UICollectionViewDelegate and UICollectionViewDataSource protocols at the HomeViewController, and then I'm configuring the cells and their nested UIViews though a configure method
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell = collectionView.dequeueReusableCell(
withReuseIdentifier: RecepieCardCollectionViewCell.identifier, for: indexPath
) as? RecepieCardCollectionViewCell else {
return UICollectionViewCell()
}
cell.configure(self.recepies[indexPath.row])
return cell
}
Then, at the RecepieCardCollectionViewCell:
func configure(_ recepie: Recepie) {
cardInfoView.configure(recepie)
thumbnail.kf.setImage(with: URL(string: recepie.getCover()))
}
And, finally, at the RecepieCardInfoView
func configure(_ recepie: Recepie) {
label.text = recepie.getName()
}
The thing is that the most deep nested UILabel at the RecepieCardInfoView, is receiving the data, but is not being updated. It always shows the same placeholder text, instead of the actual recepie name.
Things I've tried:
Placing .setNeedsDisplay() to all the element, with no results.
Using DispatchQueue.main.async but didn't work, which makes sense since I'm not using an API request to show the recepies (for now).
Move the label inside RecepieCardInfoView to the RecepieCardCollectionViewCell. This worked for some reason, but I would like to understand why.
If you need more context of the code, you can find the full repository here at the branch feat/recepies-list
I've been asked to add the full code of the cell, here it is:
import UIKit
import Kingfisher
class RecepieCardCollectionViewCell: UICollectionViewCell {
static let identifier = "RecepieCardUICollectionViewCell"
let thumbnail: UIImageView = {
let image = UIImageView()
image.translatesAutoresizingMaskIntoConstraints = false
image.kf.setImage(with: URL(string: "https://i.imgur.com/ISxVZHA.png"))
return image
}()
let content: UIView = {
let view = UIView(frame: .zero)
view.clipsToBounds = true
view.layer.cornerRadius = 40
return view
}()
let cardInfoView = RecepieCardInfoView(frame: .zero)
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .themeWhite
layer.cornerRadius = 40
addSubview(content)
content.frame = bounds
content.addSubview(thumbnail)
let cardInfoView = RecepieCardInfoView(frame: .zero)
content.addSubview(cardInfoView)
layer.shadowColor = UIColor.black.cgColor
layer.shadowOpacity = 0.17
layer.shadowOffset = .zero
layer.shadowRadius = 10
NSLayoutConstraint.activate([
thumbnail.leadingAnchor.constraint(equalTo: content.leadingAnchor),
thumbnail.trailingAnchor.constraint(equalTo: content.trailingAnchor),
thumbnail.topAnchor.constraint(equalTo: content.topAnchor),
thumbnail.heightAnchor.constraint(equalTo: thumbnail.widthAnchor),
cardInfoView.topAnchor.constraint(equalTo: content.bottomAnchor, constant: -100),
cardInfoView.bottomAnchor.constraint(equalTo: content.bottomAnchor),
cardInfoView.leadingAnchor.constraint(equalTo: content.leadingAnchor),
cardInfoView.trailingAnchor.constraint(equalTo: content.trailingAnchor),
])
}
required init(coder: NSCoder) {
fatalError()
}
func configure(_ recepie: Recepie) {
cardInfoView.configure(recepie)
thumbnail.kf.setImage(with: URL(string: recepie.getCover()))
}
}
Thanks you in advance!
You create 2 instances of cardInfoView inside RecepieCardCollectionViewCell
let cardInfoView = RecepieCardInfoView(frame: .zero) // here 1
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .themeWhite
layer.cornerRadius = 40
addSubview(content)
content.frame = bounds
content.addSubview(thumbnail)
let cardInfoView = RecepieCardInfoView(frame: .zero) // and here 2
The problem is that you add the inner view without updating it's content , and update the outer view without adding it to cell hierarchy
I am building an App using SpriteKit, thus I am only using one ViewController to add or remove subViews. And I always add a new Instance of a subview.
When I'm trying to add a UIScrollView, it shows up perfectly fine the first time I add it.
However, after I remove the UIScrollView and added it(a new instance of UIScrollView) again. The UIScrollView does not show up.
The frame of the UIScrollView and the UIStackView inside are the same for the first time and the second time.
I do not quite understand why it is not working properly. I am guessing it is related to auto-layout, but again, the frame is the same when it is added the first time and the second time.
And, I am not trying to implement auto-layout here.
Here is the class:
class StoreScrollV: UIScrollView {
override init(frame: CGRect) {
super.init(frame: rectOfEntireScreen)
self.bounds.size = CGSize(width: 300, height: 300)
self.contentSize = CGSize(width: 1000, height: 300)
self.tag = 100
let stackView = UIStackView()
self.addSubview(stackView)
stackView.tag = 111
stackView.frame.size = CGSize(width: 1000, height: 300)
stackView.frame = stackView.toCenter()
//custome function that move the view to the center of its parent with the same size.
stackView.axis = .horizontal
stackView.translatesAutoresizingMaskIntoConstraints = false
self.translatesAutoresizingMaskIntoConstraints = false
let imageV1 = UIImageView(image: UIImage(named: "ballCat"))
stackView.addArrangedSubview(imageV1)
let imageV2 = UIImageView(image: UIImage(named: "ballChicken"))
stackView.addArrangedSubview(imageV2)
stackView.spacing = 10;
stackView.distribution = .equalCentering
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func willMove(toWindow newWindow: UIWindow?) {
if newWindow != nil {
// joinAnimationFromTop(view: self)
} else {
// leaveAnimationResetToTop(view: self)
}
}
}
Here is how I add it:
(the UIScrollView is inside another UIView that gets added)
let storePage = StoreView() //another customized UIView frame is the entire screen at 0,0.
let scrV = StoreScrollV()
storePage.addSubview(scrV)
scrV.frame = scrV.toCenter()
//custome function that move the view to the center of its parent with the same size.
VC.addSubview(viewWithScrollV)
hierarchy debugging the second time ScrollView is added
hierarchy debugging the second time ScrollView is added
stackView.translatesAutoresizingMaskIntoConstraints = false
self.translatesAutoresizingMaskIntoConstraints = false
change to
stackView.translatesAutoresizingMaskIntoConstraints = true
self.translatesAutoresizingMaskIntoConstraints = true
Problem fixed.
The frame for the scrollView is actually (0,0,0,0). I did not realize this because when I was debugging, I printed out the frame of the scrollView at the end of the init function.
Thus the frame was correct. When I used view hierarchy debugging suggested by Kamil, I realized that the frame has always been wrong.
However, I still do not understand why would it even show up the first time around if the frame is (0,0,0,0).
We are currently working in an older codebase for our iOS application and are running into a weird bug where the UIScrollViews paging is not matching on the initialization but only once a user selects the button to change the view.
Expected Result:
The result we have:
Each ScrollView has three slides nested inside of them. We initialize the ScrollView like this:
override init(frame: CGRect) {
super.init(frame: frame)
self.commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.commonInit()
}
private func commonInit() {
Bundle.main.loadNibNamed("DIScrollView", owner: self, options: nil)
contentView.frame = self.bounds
addSubview(contentView)
contentView.autoresizingMask = [.flexibleHeight,.flexibleWidth]
contentView.layer.borderColor = UIColor.white.cgColor
contentView.layer.borderWidth = 2.0
scrollView.delegate = self
setUpScrollViewer()
}
You can see we call to set up the ScrollView and that is done like this:
public func setUpScrollViewer() {
let slides = self.createSlides()
let defaultIndex = 1
scrollView.Initialize(slides: slides, scrollToIndex: defaultIndex)
pageControl.numberOfPages = slides.count
pageControl.currentPage = defaultIndex
}
Now that all the content is available for each slide, we want to handle the content and we do so with a ScrollView extension:
extension UIScrollView {
//this function adds slides to the scrollview and constraints to the subviews (slides)
//to ensure the subviews are properly sized
func Initialize(slides:[UIView], scrollToIndex:Int) {
//Take second slide to base size from
let frameWidth = slides[1].frame.size.width
self.contentSize = CGSize(width: frameWidth * CGFloat(slides.count), height: 1)
for i in 0 ..< slides.count {
//turn off auto contstraints. We will be setting our own
slides[i].translatesAutoresizingMaskIntoConstraints = false
self.addSubview(slides[i])
//pin the slide to the scrollviewers edges
if i == slides.startIndex {
slides[i].leadingAnchor.constraint(equalTo: self.leadingAnchor).isActive = true
} else { //pin each subsequent slides leading edge to the previous slides trailing anchor
slides[i].leadingAnchor.constraint(equalTo: slides[i - 1].trailingAnchor).isActive = true
}
slides[i].topAnchor.constraint(equalTo: self.topAnchor).isActive = true
slides[i].widthAnchor.constraint(equalTo: self.widthAnchor).isActive = true
slides[i].heightAnchor.constraint(equalTo: self.heightAnchor).isActive = true
}
//the last slides trailing needs to be pinned to the scrollviewers trailing.
slides.last?.trailingAnchor.constraint(equalTo: self.trailingAnchor).isActive = true
self.scrollRectToVisible(CGRect(x: frameWidth * CGFloat(scrollToIndex), y: 0, width: frameWidth, height: 1), animated: false)
}
}
I have tried manually setting contentOffset and nothing seems to be adjusting on the initialization. If the user selects the button it hides and then unhides it to display it properly with no logic adjusting this. Giving me the impression this issue is on the init.
Summary:
When the main view loads, the scrollView is showing me the first slide in the index when i need to be focused on the second slide. However if the user hides and then unhides the scrollView it works as intended.
How do i get the UIScrollView to actually load and initialize updating the scrollView to show the second slide and not initialize on the first slide?
Try explicitely running the scrollRectToVisible in the main thread using
DispatchQueue.main.async {
}
My guess is that all this code runs before the views are positioned by the layout system, and the first slide’s frame is the default 0 x 0 size. When the app returns to this view auto layout has figured out the size of this slide, so the calculation works.
Tap into the layout cycle to scroll to the right place after the layout. Maybe override viewDidLayoutSubviews() to check if it’s in the initial layout and then set the scroll position.
Use constraints for your contentView instead setting frame and autoresizingMask.
Call view.layoutIfNeeded() in the viewController before scrollRectToVisible or setContentOffset(I prefer the last)
I am trying to apply background Image to all my screens in my iOs application.
My initial screen is a Registration screen where i want to apply the background image, the registration screen is as shown below:
I am using a custom UIView from xib file to apply the background image.
But after applying the background image it only shows the background image on the Registration screen and removes the login controls. I dont know why.
Following is the registration screen after applying the image background.
One more thing, I can see the background image only on the run time not in the Storyboard.
Following is my implementation
I have created one .xib file having a simple UIView, this UIView would serve as a master view in all my application. To obtain the UIView from nib file and to apply further settings I am using a custom UIView class. In this custom UIView class I am applying the background image on the UIView obtained from the nib file. As you can see above I am applying this custom UIView class to the Registration screen UIView. Following is the code as shown below:
class BackgroundMap: UIView {
var view: UIView!
override init(frame: CGRect) {
super.init(frame: frame)
view = loadViewFromNib ()
view.frame = bounds
view.autoresizingMask = [.FlexibleWidth, .FlexibleHeight]
view.translatesAutoresizingMaskIntoConstraints = true
view.backgroundColor = UIColor(patternImage: UIImage(named: "MapWantd")!)
self.addSubview(view)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
view = loadViewFromNib ()
view.frame = bounds
view.autoresizingMask = [.FlexibleWidth, .FlexibleHeight]
view.translatesAutoresizingMaskIntoConstraints = true
view.backgroundColor = UIColor(patternImage: UIImage(named: "MapWantd")!)
self.addSubview(view)
}
func loadViewFromNib() ->UIView {
let bundle = NSBundle(forClass: self.dynamicType)
let nib = UINib(nibName: "BackgroundMap", bundle: bundle)
let view = nib.instantiateWithOwner(self, options: nil)[0] as! UIView
return view
}
}
I have a UIImageView, and a series of conflicting constraints set on it.
I set the active property of some of them to true or to false at different times, and it works—the image view is always where it's supposed to be.
However, in another method, I use it's frame to calculate the position of another view, and so I noticed that it's frame isn't where the image view appears. For example, the image view appears centered in the middle of the screen, but it's frame (I created another UIView and set it's frame to the image view's frame to test this), is in the bottom left—where the image view used to be before changing which constraints were active.
Is there a way I can update the frame so that it reflects where the image view actually is? Or, is there a way to get the true frame of the image view?
Here's the code (qrCode is the view I'm trying to arrange, myContainer is its superview):
self.qrCode.setTranslatesAutoresizingMaskIntoConstraints(false)
//this sets qrCode 0 pts from the bottom of its parent
self.bottom.active = false
//this centers qrCode vertically in its parent
self.center.active = true
//these set how far the parent is from the edges of its view
self.newLeft.active = true
self.newRight.active = true
let testView = UIView(frame: qrCode.frame)
testView.backgroundColor = UIColor.greenColor()
self.myContainer.addSubview(testView)
All this code positions the image view (qrCode) correctly, but it's frame (as shown by testView is in the wrong location—it's where qrCode was before the constraints were configured.
Here's the answer:
Do this in your viewdidload:
#IBOutlet weak var asdf = QQCustomUIViewForQRImageView()
override func viewDidLoad() {
super.viewDidLoad()
var floatiesW = 200 as CGFloat
var floatiesH = 200 as CGFloat
asdf!.frame = CGRectMake((self.view.bounds.width/2.0) - (floatiesW/2.0), (self.view.bounds.height/2.0) - (floatiesH/2.0), floatiesW, floatiesH)
asdf!.qrImageView.image = UIImage(named: "check")
}
Here's the UIView you'll need to use as a subclass, as per our conversation:
import UIKit
class QQCustomUIViewForQRImageView: UIView {
var qrImageView = UIImageView()
override init (frame : CGRect) {
super.init(frame : frame)
}
convenience init () {
self.init(frame:CGRect.zeroRect)
}
override func layoutSubviews() {
super.layoutSubviews()
self.backgroundColor = UIColor.redColor()
qrImageView.setTranslatesAutoresizingMaskIntoConstraints(false)
qrImageView.backgroundColor = UIColor.yellowColor()
self.addSubview(qrImageView)
let views: [NSObject : AnyObject] = [ "qrImageView" : qrImageView]
self.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|-4-[qrImageView]-4-|", options: nil, metrics: nil, views: views))
self.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-4-[qrImageView]-4-|", options: nil, metrics: nil, views: views))
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
that's 4 points on each side, this will resize for you, and the iamgeview is set with an image like I show in the view did load call above