Swift Class Doesn't Like Self.View.Addsubview()

Swift class doesn't like self.view.addSubview()

You are doing it wrong. Your class does not have view property. I guess you were trying to use view controller. So every viewcontroller has a property named view of type UIView which is what you are looking for.

class MyViewController: UIViewController {
var buttonLabel:String
var buttonRadius:CGFloat = 90
var button = UIButton.buttonWithType(UIButtonType.System) as UIButton

init(buttonColour:CGColorRef, buttonLabel:String){
self.buttonColour = buttonColour
self.buttonLabel = buttonLabel
}

func drawButton() {
button.frame = 100, 100, buttonRadius * 2, buttonRadius * 2)
button.layer.borderColor = buttonColour
button.setTitle(buttonLabel, forState: UIControlState.Normal)
button.addTarget(self, action: "pressed:", forControlEvents: UIControlEvents.TouchUpInside)
self.view.addSubview(button) // error: 'ViewController.myButton' does not have a member named 'view'
}
}

How to add views while using a function in a separate class in Swift?

Change

class InitialiseLabels

To

extension ViewController

Even if this is in another file, it has to be part of the original ViewController class, not some other class.

Subview button is not working

I believe the height and width of the subview remains 0 because the button is not bound to any edges and the button seem to define the height of its superview. You could always check this by settings clipToBounds = true. Its always good to call the lazy if you are using self within an view.

This should solve your problem:

class buttonView: UIView {
private lazy var button: UIButton = {
let button = UIButton()
button.setTitleColor(.gray, for: .normal)
button.setTitle("MyButton", for: .normal)
button.translatesAutoresizingMaskIntoConstraints = false
button.addTarget(self, action: #selector(buttonAction), for: .touchUpInside)
return button
}()

override init(frame: CGRect) {
super.init(frame: frame)
setup()
}

required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}

func setup() {
addSubview(button)

addConstraint(NSLayoutConstraint(item: button, attribute: .top, relatedBy: .equal, toItem: self, attribute: .top, multiplier: 1, constant: 10))
addConstraint(NSLayoutConstraint(item: button, attribute: .leading, relatedBy: .equal, toItem: self, attribute: .leading, multiplier: 1, constant: 10))
addConstraint(NSLayoutConstraint(item: button, attribute: .bottom, relatedBy: .equal, toItem: self, attribute: .bottom, multiplier: 1, constant: 0))
addConstraint(NSLayoutConstraint(item: button, attribute: .trailing, relatedBy: .equal, toItem: self, attribute: .trailing, multiplier: 1, constant: 0))
addConstraint(NSLayoutConstraint(item: button, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 80))
addConstraint(NSLayoutConstraint(item: button, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 35))
}

func buttonAction() {
//Do stuff
}
}

Slightly unsure about the NSLayoutConstraints, since i use SnapKit or anchorpoints. But i think this should be correct.

Properties for custom UIView class not being set when called and added to a UIScrollview

You can get closer to what you want by changing your loadSlides() function to:

func loadSlides() -> [UIView] {

// instantiate with a frame
let container1 = ScrollViewSlide(frame: scrollView.frame)
container1.mainImageForSubview.image = UIImage(named: "cricket1")
container1.mainLabel.text = "Container1"

// instantiate with a frame
let container2 = ScrollViewSlide(frame: scrollView.frame)
container2.mainImageForSubview.image = UIImage(named: "cricket2")
container2.mainLabel.text = "Container2"

// move container2 to the right of container1
container2.frame.origin.x = container1.frame.origin.x + container1.frame.size.width

// instantiate with a frame
let container3 = ScrollViewSlide(frame: scrollView.frame)
container3.mainImageForSubview.image = UIImage(named: "crisp_ocean")
container3.mainLabel.text = "Container3"

// move container3 to the right of container2
container3.frame.origin.x = container2.frame.origin.x + container2.frame.size.width

return [container1, container2, container3]
}

However, as I mentioned in comments, you'll be much better off in the long run by learning about auto-layout.


Edit

Here is your code, modified to use auto-layout instead of explicit frames:

class ScrollViewSlide: UIView {

lazy var mainImageForSubview : UIImageView = {
let mI = UIImageView()
return mI
}()

lazy var mainLabel : UILabel = {
let mL = UILabel()
return mL
}()

override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}

required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

private func setupView() {
backgroundColor = .white
contentMode = .scaleAspectFit
addSubview(mainImageForSubview)
addSubview(mainLabel)

mainImageForSubview.translatesAutoresizingMaskIntoConstraints = false
mainLabel.translatesAutoresizingMaskIntoConstraints = false

NSLayoutConstraint.activate([

// constrain main image to all 4 sides
mainImageForSubview.topAnchor.constraint(equalTo: topAnchor),
mainImageForSubview.bottomAnchor.constraint(equalTo: bottomAnchor),
mainImageForSubview.leadingAnchor.constraint(equalTo: leadingAnchor),
mainImageForSubview.trailingAnchor.constraint(equalTo: trailingAnchor),

// constrain main label centered X and Y, height = 30
mainLabel.centerXAnchor.constraint(equalTo: centerXAnchor),
mainLabel.centerYAnchor.constraint(equalTo: centerYAnchor),
mainLabel.heightAnchor.constraint(equalToConstant: 30.0),

])
}
}

class SlidesViewController: UIViewController, UIScrollViewDelegate {

// MARK: - Properties

// using auto-layout, so no need for contentViewSize
//lazy var contentViewSize = CGSize(width: self.view.frame.width * 3, height: self.view.frame.height * 0.6)

// MARK: - Views

lazy var pageIndicator : UIPageControl = {
let pI = UIPageControl()
pI.frame = CGRect()
return pI
}()

lazy var scrollView : UIScrollView = {
let view = UIScrollView(frame: .zero)
view.backgroundColor = .white
view.delegate = self
return view
}()

lazy var stackView: UIStackView = {
let v = UIStackView()
v.axis = .horizontal
v.alignment = .fill
v.distribution = .fill
v.spacing = 0
return v
}()

// MARK: - View Controller Lifecycle

override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
view.backgroundColor = .white

view.addSubview(scrollView)
scrollView.addSubview(stackView)

scrollView.translatesAutoresizingMaskIntoConstraints = false
stackView.translatesAutoresizingMaskIntoConstraints = false

let mySlides = loadSlides()

setUpViewsForScrollView(slides: mySlides)

loadPageIndicator(mySlides)

let v = view.safeAreaLayoutGuide
let g = scrollView.contentLayoutGuide

NSLayoutConstraint.activate([

// constrain scroll view to top, leading, trailing
scrollView.topAnchor.constraint(equalTo: v.topAnchor),
scrollView.leadingAnchor.constraint(equalTo: v.leadingAnchor),
scrollView.trailingAnchor.constraint(equalTo: v.trailingAnchor),

// constrain scroll view height to 60% of view height
scrollView.heightAnchor.constraint(equalTo: v.heightAnchor, multiplier: 0.6),

// constrain stack view to all 4 sides of scroll view's contentLayoutGuide
stackView.topAnchor.constraint(equalTo: g.topAnchor),
stackView.bottomAnchor.constraint(equalTo: g.bottomAnchor),
stackView.leadingAnchor.constraint(equalTo: g.leadingAnchor),
stackView.trailingAnchor.constraint(equalTo: g.trailingAnchor),

// constrain stack view height equal to scroll view height
stackView.heightAnchor.constraint(equalTo: scrollView.heightAnchor),

])

scrollView.isPagingEnabled = true

}

func loadSlides() -> [UIView] {

let container1 = ScrollViewSlide()
container1.mainImageForSubview.image = UIImage(named: "cricket1")
container1.mainLabel.text = "Container1"

let container2 = ScrollViewSlide()
container2.mainImageForSubview.image = UIImage(named: "cricket2")
container2.mainLabel.text = "Container2"

let container3 = ScrollViewSlide()
container3.mainImageForSubview.image = UIImage(named: "crisp_ocean")
container3.mainLabel.text = "Container3"

return [container1, container2, container3]
}

func setUpViewsForScrollView(slides : [UIView]) {
for container in slides {
stackView.addArrangedSubview(container)
container.widthAnchor.constraint(equalTo: scrollView.widthAnchor).isActive = true
}
}

func loadPageIndicator(_ mySlides : [UIView]) {
let myPageIndicator = pageIndicator
myPageIndicator.numberOfPages = mySlides.count
myPageIndicator.backgroundColor = .black

self.view.addSubview(myPageIndicator)

myPageIndicator.translatesAutoresizingMaskIntoConstraints = false

NSLayoutConstraint.activate([
// width = 100, height = 30
myPageIndicator.widthAnchor.constraint(equalToConstant: 100.0),
myPageIndicator.heightAnchor.constraint(equalToConstant: 30.0),
// constrain top to 12-pts below bottom of scroll view
myPageIndicator.topAnchor.constraint(equalTo: scrollView.bottomAnchor, constant: 12.0),
// constrain centerX to centerX of scroll view
myPageIndicator.centerXAnchor.constraint(equalTo: scrollView.centerXAnchor, constant: 0.0),
])
}

func scrollViewDidScroll(_ scrollView: UIScrollView) {
let pageIndex = round(scrollView.contentOffset.x/view.frame.width)
pageIndicator.currentPage = Int(pageIndex)
}
}

Swift sub view hold old values (stacking new values on top of it)

Not sure from where you call displayProgressBar as its not clear from your question, what you can do is add these statement as either 1st statment in displayProgressBar or as a statement before calling displayProgressBar

Approach 1:

self.progressScrollView?.removeFromSuperview() //? because for the first time implicit optional will be nil
self.progressScrollView = UIScrollView()

In ViewDidLoad you have self.view.removeFromSuperview() which I have no idea why? Makes no sense to me why would you try to remove ViewController's view? May be you were trying to remove subviews I assume, if yes, thats not the proper way to remove subviews, you iterate over subViews and call removeFromSuperview on each subView, but I dont think you need that, clearly from your code you hold a reference to progressScrollView somewhere in your code, just remove it from subview and recreate the progressScrollView as

progressScrollView = UIScrollView()

And if you are not holding a reference to progressScrollView and instead you were creating it as only local variable in displayProgressBar method, hold a reference to this view, to do that declare a instance variable var progressScrollView: UIScrollView!

Approach 2:

And if you dont prefer creating progressScrollView() again and again, you can always iterate over its subviews and remove them from superview one by one

        for view in progressScrollView.subviews {
view.removeFromSuperview()
}

Make sure you run self.view.addSubview(progressScrollView) only once (like in ViewDidLoad or somewhere) if you decide to go with approach 2.

In either case you need to hold a reference to progressScrollView

UIViewController with custom UIView (that includes a button) doesn't recognize taps

You forgot set height for BaseHeader

 NSLayoutConstraint.activate([
header.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
header.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
header.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor, constant: -10),
header.heightAnchor.constraint(equalToConstant: 40) //add more
])

Would it cause retain cycle that add a subview on a view passed by parameter of a function?

What is Retain Cycle? - It's the condition when 2 objects keep a reference to each other and are retained, it creates a retain cycle since both objects try to retain each other, making it impossible to release.

In this case, self.customViewClass keep a reference to self.view but self.view doesn't keep any reference to self.customViewClass. Which keeps reference to self.customViewClass is self, not self.view.

So of course, it won't causes retain cycle

Don't believe in me ? /strong> - Check it yourself by trying to log something inside dealloc method.

After you dismiss CustomViewController, if the code inside dealloc is called and log something, it means no retain cycle here. If not, it causes retain cycle.

For example

- (void)dealloc {
NSLog(@"BOOM RIGHT ANSWER!!!");
}

Adding a view controller as a subview in another view controller

A couple of observations:

  1. When you instantiate the second view controller, you are calling ViewControllerB(). If that view controller programmatically creates its view (which is unusual) that would be fine. But the presence of the IBOutlet suggests that this second view controller's scene was defined in Interface Builder, but by calling ViewControllerB(), you are not giving the storyboard a chance to instantiate that scene and hook up all the outlets. Thus the implicitly unwrapped UILabel is nil, resulting in your error message.

    Instead, you want to give your destination view controller a "storyboard id" in Interface Builder and then you can use instantiateViewController(withIdentifier:) to instantiate it (and hook up all of the IB outlets). In Swift 3:

    let controller = storyboard!.instantiateViewController(withIdentifier: "scene storyboard id")

    You can now access this controller's view.

  2. But if you really want to do addSubview (i.e. you're not transitioning to the next scene), then you are engaging in a practice called "view controller containment". You do not just want to simply addSubview. You want to do some additional container view controller calls, e.g.:

    let controller = storyboard!.instantiateViewController(withIdentifier: "scene storyboard id")
    addChild(controller)
    controller.view.frame = ... // or, better, turn off `translatesAutoresizingMaskIntoConstraints` and then define constraints for this subview
    view.addSubview(controller.view)
    controller.didMove(toParent: self)

    For more information about why this addChild (previously called addChildViewController) and didMove(toParent:) (previously called didMove(toParentViewController:)) are necessary, see WWDC 2011 video #102 - Implementing UIViewController Containment. In short, you need to ensure that your view controller hierarchy stays in sync with your view hierarchy, and these calls to addChild and didMove(toParent:) ensure this is the case.

    Also see Creating Custom Container View Controllers in the View Controller Programming Guide.


By the way, the above illustrates how to do this programmatically. It is actually much easier if you use the "container view" in Interface Builder.

Sample Image

Then you don't have to worry about any of these containment-related calls, and Interface Builder will take care of it for you.

For Swift 2 implementation, see previous revision of this answer.



Related Topics



Leave a reply



Submit