I'm trying to subclass UIScrollView, to do some custom drawing and creation of customized UIViews. The drawing and creation of UIViews works fine, but the view just doesn't scroll.
The internal height of the view is fixed, and I calculate it in the init method. I also override the intrinsticContentSize method, but that doesn't work.
What am I doind wrong?
import UIKit
class CustomView: UIScrollView, UIScrollViewDelegate {
// MARK: - layout constants
private var _intrinsicSize: CGSize?;
override init(frame: CGRect) {
super.init(frame: frame);
self.didLoad();
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder);
self.didLoad();
}
private func didLoad() {
self.delegate = self;
var result = CGSize();
result.height = CGFloat(_halfHourHeight * 48);
result.width = 500;
_intrinsicSize = result;
}
override func intrinsicContentSize() -> CGSize {
return self._intrinsicSize!;
}
override func drawRect(rect: CGRect) {
super.drawRect(rect);
// some custom drawing here
}
}
Scroll views generally don't have an intrinsic size, it usually doesn't mean anything. They have a frame, bounds and content size - it's the content size you're interested in setting and it goes into setting the bounds.
The content size is the total size of all the subviews, and the bounds is the window onto the currently visible area of those subviews.
You also wouldn't usually have custom drawing code, though you can. You'd usually add subviews to do that drawing for the scroll view.
Related
I've been trying to solve this problem for a while but I cannot find a solution. The thing is I have a custom UITextField with custom properties, for instance, background color, and I reuse this control in my storyboards. The problem is when I run the app on the simulator or device, there is a little delay at runtime between the default storyboard values and the values I set in my custom control. For example, if I drag a UITextField to my view controller in my storyboard, leave all the properties to their default values and just set my custom control class in the Identity Inspector, when I run the code in either the simulator or my device, I see a display delay between the default white background color for this UITextField and the custom one set in my custom class. Is someone experiencing this issue?
Here a snippet of my class:
import UIKit
import SwifterSwift
#IBDesignable
class TextField: UITextField {
// MARK: - Properties
// Left padding for text and placeholder
#IBInspectable var textPadding = Constants.Views.textFieldTextPadding {
didSet {
setNeedsDisplay()
}
}
// MARK: - Property Overrides
// change the default height
override var intrinsicContentSize: CGSize {
var contentSize = super.intrinsicContentSize
contentSize.height = Constants.Views.textFieldHeight
return contentSize
}
// MARK: - Initializers
// IBDesignables require both of these inits, otherwise we'll
// get an error: IBDesignable View Rendering times out.
// http://stackoverflow.com/questions/26772729/ibdesignable-view-rendering-times-out
override init(frame: CGRect) {
super.init(frame: frame)
setupDefaults()
}
// This attribute hides `init(coder:)` from subclasses
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupDefaults()
}
// MARK: - Method Overrides
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
setupDefaults()
}
//provides left padding for the text
override func textRect(forBounds bounds: CGRect) -> CGRect {
return super.textRect(forBounds: bounds).insetBy(dx: textPadding, dy: 0)
}
// provides left padding for the editing text
override func editingRect(forBounds bounds: CGRect) -> CGRect {
return textRect(forBounds: bounds)
}
// provides left padding for the placeholder text
override func placeholderRect(forBounds bounds: CGRect) -> CGRect {
return textRect(forBounds: bounds)
}
// MARK: - Private Methods
func setupDefaults() {
// set default font
font = Constants.Views.textFieldFont
// set default colors
textColor = Constants.Views.textFieldTextColor
tintColor = textColor // default for image tinting and cursor color
backgroundColor = Constants.Views.textFieldBackgroundColor
// set border style
borderStyle = UITextBorderStyle.none
// rounded corners first, order matters if setting shadow
cornerRadius = Constants.Views.cornerRadius
// set the shadow
setShadow(color: Constants.Views.shadowColor, radius: Constants.Views.shadowRadius,
offset: Constants.Views.shadowOffset, opacity: Constants.Views.shadowOpacity)
// clear button
clearButtonMode = .whileEditing
// show a Done button in the keyboard to dismiss it
showDoneButton = true
}
}
I have this:
class SheetView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
RCTLogInfo("SHEET VIEW INIT'ED")
let width = String(format:"%.3f", Double(self.frame.size.width));
let height = String(format:"%.3f", Double(self.frame.size.height));
RCTLogInfo("Width: " + width + ", Height: " + height);
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
However width and height are always 0. Reading here Access UIView width at runtime it seems I need a viewDidLayoutSubviews but a UIView doesn't seem to have this. I have to use UIViewController to use viewDidLayoutSubviews, however I cannot change from UIView, because the place this component gets used, expects a UIView:
class SheetViewManager : RCTViewManager {
override func view() -> UIView! {
return SheetView();
}
}
Is there anyway to get height/width in just the UIView?
Yes you sure can. Try this . (Swift 3.0)
override func layoutSubviews() {
super.layoutSubviews()
print(self.frame)
}
Make sure you know that if your custom view's superview doesn't give your custom view a frame either by frame setting or by autolayout via storyboard or programmatically, your frame will remain at 0 width and 0 height
Here i have created UIView subclass named as CustomSlider. CustomSlider added on Storyboard and connected with storyboard outlet reference. When i am printing the bounds, I am having issues with printing the bounds, its providing incorrect console output.
class CustomSlider: UIView {
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
}
override init(frame: CGRect) {
super.init(frame: frame)
self.setupView()
}
func setupView() {
print("self.bounds.height \(self.bounds)" )
self.backgroundColor = UIColor.red
}
}
class ViewController: UIViewController {
#IBOutlet weak var slider: CustomSlider?
override func viewDidLoad() {
super.viewDidLoad()
slider?.setupView()
}
}
When i am trying to build the output bounds displaying as below :
self.bounds.height (0.0, 0.0, 1000.0, 1000.0)
But i'm setting constraints trailing, top, right, height. I have tried below line also.
slider?.translatesAutoresizingMaskIntoConstraints = false
here is screenshot my project looks like
My expected console output would be the slider bounds.
Your call to print the bounds comes too soon. A view being loaded from a xib/storyboard has bogus bounds (as shown) until layout has occurred. Layout has not occurred at the time of viewDidLoad; the view is not yet in the interface and no sizes are real yet. Postpone your access to bounds until at least after viewDidLayoutSubviews has been called for the first time.
Summary: I have a child view within a stackview and that child view that is programatically sized. It has a couple of empty views to fill up the empty space. At runtime, however, attempts to resize the child don't work and it remains the same size as in the storyboard. How can I resize the child view so that the stack view honours its new size and positions it accordingly.
Details:
I have a UICollectionView with a custom layout. The layout calculates the positions of subviews correctly (they display where I want) and return the correct content size. The content size is narrower than the screen but possibly longer depending upon orientation.
The custom layout returns the correct, calculated size but the collection view does not resize.
I've tried programatically changing the collection view's size on the parent's viewDidLoad. Didn't work.
I've tried programatically changing the collection view's layoutMargins on the parent's viewDidLoad. Didn't work.
I am using Swift 3, XCode 8.
If I understand your question, you need your UICollectionView to have its size equals to its content, in other words, you want your UICollectionView to have an intrinsic size (just like a label which resizes automatically based on its text).
In that case, you can subclass UICollectionView to add this behaviour, then you don't need to set its size.
class IntrinsicSizeCollectionView: UICollectionView {
// MARK: - lifecycle
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.setup()
}
override init(frame: CGRect, collectionViewLayout layout: UICollectionViewLayout) {
super.init(frame: frame, collectionViewLayout: layout)
self.setup()
}
override func layoutSubviews() {
super.layoutSubviews()
if !self.bounds.size.equalTo(self.intrinsicContentSize) {
self.invalidateIntrinsicContentSize()
}
}
override var intrinsicContentSize: CGSize {
get {
let intrinsicContentSize = self.contentSize
return intrinsicContentSize
}
}
// MARK: - setup
func setup() {
self.isScrollEnabled = false
self.bounces = false
}
}
ps: In your .xib, don't forget to set your IntrinsicSizeCollectionView height constraint as a placeholder constraint (check "Remove at build time")
I have some weird behavior related to frame sizes that I can't fix after hours of trying different things. This just doesn't make any sense.
I have a custom UIView and a related .xib file:
ErrorView.swift
import UIKit
class ErrorView: UIView {
#IBOutlet weak var labelErrorTitle: UILabel!
#IBOutlet weak var view: UIView!
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
setupView()
}
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}
func setupView() {
NSBundle.mainBundle().loadNibNamed("ErrorView", owner: self, options: nil)
self.addSubview(view)
}
ErrorView.xib (using inferred size, the label is centered using constraints)
I want to add this view to a custom UITableView, at the bottom. I want to make it slim, with a height of 45 and a width of the view screen width. I want to add it to the bottom.
Very easy!! I just set the size with a frame like this:
class LoadingTableView: UITableView {
var errorView: ErrorView = ErrorView () // Here is the Error View
var errorFrame: CGRect! // The frame (size) I will use
required init(coder aDecoder: NSCoder) {
}
override func awakeFromNib() {
super.awakeFromNib()
// I create the frame here to put the error at the top
errorFrame = CGRectMake(0,0,self.frame.width,45)
// Init the ErrorView
errorView = ErrorView(frame: errorFrame)
// I add the subview to root (I have this rootView created)
rootView?.addSubview(errorView)
}
// This is needed, it updates the size when layout changes
override func layoutSubviews() {
super.layoutSubviews()
// Create the size again
errorFrame = CGRectMake(0,0,self.frame.width,45)
// I update the frame
errorView.frame = frame
}
This should work but it doesn't. The size is just weird. It takes the size from the nib which is 320x568. Then it just moves the frame, but the size doesn't change.
And here comes the great part. If I set the errorView.frame size to .frame, which is the frame of the tableView then it works! With orientation changes and all!
But as long as I change the frame to a custom size, whatever is in awakeFromNib or layoutSubviews it doesn't and starts to act weird.
Why does it keeps the size of the nib? And why if I put .frame it works at it should but a custom size doesn't? It looks like I'm super close, it's frustrating as hell.
The objective is to say tableView.error("errorCode") and then that errorView appears. And it works on all devices and orientations.
Instead of adding a subview to UITableView, use it's tableFooterView and tableHeaderView properties:
Replace
rootView?.addSubview(errorView)
With:
tableFooterView = errorView