ScrollView Constraint problem threw storyboard - swift

So I maybe successfully created a scroll view following a tutorial but something is terribly wrong. So I'm trying to create a scroll view with a table view inside and some buttons and labels and everything works fine but as soon as I add a single constraint it all goes just white with no explanation. I would assume content view is messing things up but I'm not sure, thx in advance!
So following some other people problems I tried filling constraints programmatically and doing views and subviews programmatically aswell, keep in mind tho that rest of constraints I did on storyboard. Btw I have tried equal width and height on newView --> scroll view and nothing seems to change.
my view hierarchy looks like this
myViewController -> View -> scrollView -> newView
class ThirdViewController: UIViewController {
#IBOutlet weak var scrollView: UIScrollView!
override func viewDidLoad() {
super.viewDidLoad()
scrollView.addSubview(newView)
newView.leftAnchor.constraint(equalTo: scrollView.leftAnchor).isActive = true
newView.topAnchor.constraint(equalTo: scrollView.topAnchor).isActive = true
newView.rightAnchor.constraint(equalTo: scrollView.rightAnchor).isActive = true
newView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor).isActive = true
//tableView1.dataSource = self
//tableView1.delegate = self
//tableView1.register(UITableViewCell.self, forCellReuseIdentifier: "cell1")
}
#IBOutlet weak var newView: UIView!
override func viewWillLayoutSubviews() {
scrollView.contentSize = CGSize(width: 375, height: 1950)
}
}
Sorry, if question is layout pretty badly - I'm not that pro!

Related

How to use a button created in XIB custom view to control setContentOffset in viewController.swift?

I followed this tutorial to create a Snapchat-Like Menu: https://www.youtube.com/watch?v=1_daE3IL_1s
In short, the idea was to create custom views with XIB, and add them into a scrollview.
I would like to add a "back" button to one of the custom views so that the scrollview will automatically scroll back to its initial view when the button is tapped. The Idea I had was to write a code inside the custom view's IBAction function to call UIScrollview's content offset delegate function.
the following is the code in View Controller:
class ViewController: UIViewController, UIScrollViewDelegate {
//I'm naming the Scroll View as "scrollView"
#IBOutlet weak var scrollView: UIScrollView! {
didSet{
scrollView.delegate = self
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
var v1 : View1 = View1(nibName: "View1", bundle: nil)
var v2 : View2 = View2(nibName: "View2", bundle: nil)
self.addChild(v1)
self.scrollView.addSubview(v1.view)
v1.didMove(toParent: self)
self.addChild(v2)
self.scrollView.addSubview(v2.view)
v1.didMove(toParent: self)
var v2Frame : CGRect = v2.view.frame
v2Frame.origin.x = self.view.frame.width
v2.view.frame = v2Frame
self.scrollView.contentSize = CGSize(width: self.view.frame.width * 2, height: self.view.frame.height)
// self.scrollView.contentSize = CGSize(self.view.frame.width * 2, self.view.frame.size.height)
}
}
The following ht the code in View2:
class View2: UIViewController {
#IBOutlet weak var buttonHome: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func buttonHomeAction(_ sender: Any) {
//how to access scrollview.setContentOffset from ViewController to scroll the view back to the first view?
}
So my question is how to access scrollview.setContentOffset from ViewController to scroll the view back to the first view? Am I on the right track? Or are there any alternative methods where I can tap a button and scroll the view back to its first page?
Thank you!
I can think of two good options here:
1) Hold a weak reference in View2 of the scrollView. in View2 add:
weak var scrollView: UIScrollView?
and in ViewController, after view2 creation add:
view2.scrollView = self.scrollView
now, in buttonHomeAction you'll have access to the scrollView.
2) If you are familiar with Apple's delegation pattern, you can add a delegate, which can be a weak reference of the presenting view controller.
Create a protocol for the delegate, using named by the view holds the delegate. so, in this case: View2Delegate
and add a method that describes the action.
protocol View2Delegate: AnyObject {
func buttonTapped()
}
so, in View2 add:
weak var delegate: View2Delegate?
and in ViewController, after view2 creation add:
view2.delegate = self
now, in ViewController implement the protocol method and access scrollView:
extension ViewController: View2Delegate {
func buttonTapped() {
// access scrollView as self.scrollView
}
}

Swift - Display header with animation

I'm kinda new to Swift coding and I wanted to train myself by finding some layouts and try to implement them using AutoLayout.
Here is a screen shot of a screen I try to implement
https://i.imgur.com/9yzvnzp.png
The behaviour is wanted here is that on scrolling the picture should fade away and the title should be set like so
https://i.imgur.com/bLhyTGs.png
Could anybody help me on how am I supposed to do that ?
Should I use a ScrollView ? UITableView ? UICollectionView ?
I'm actually working on an application that does something similar.
So, the first thing you want to do is create a UIView for the background, i.e. the blue part that shows Squirtle. This view can take up the entire view controller. Connect it to your UIViewController via IBOutlet and call it backgroundView.
Next, we want to lay a UIScrollView over backgroundView. This scroll view should be constrained to the top, bottom, leading, and trailing edges of the superview so that it covers the entire view controller's frame. Connect this UIScrollView to your UIViewController via IBOutlet and name it scrollView. Also, make sure that scrollView's backgroundColor is set to clear. This way, we'll be able to see backgroundView underneath our scrollView.
Within your UIViewController, you'll want to set scrollView's delegate to self inside of viewDidLoad. Your UIViewController's code should now look something like this:
class SquirtleViewController: UIViewController {
#IBOutlet var backgroundView: UIView!
#IBOutlet var scrollView: UIScrollView!
override func viewDidLoad() {
super.viewDidLoad()
scrollView.delegate = self
}
}
extension SquirtleViewController: UIScrollViewDelegate {
}
We'll set delegate methods for scrollView later. These will help us know when the scroll view has begun scrolling to the top of our SquirtleViewController.
Now, our scrollView has a clear background, and doesn't actually display anything to our user. This is how we want it for now. We want the top of our scrollView to be clear so that we can see backgroundView behind it, and the bottom of our scrollView to have another view, i.e. the "content view" that shows STATS, EVOLUTIONS, MOVES, HP, etc.
So, let's add another UIView as a subview of our scrollView. Connect it to SquirtleViewController via IBOutlet and name it contentView.
Now, we need to create a constraint between contentView's top edge and scrollView's top edge. This constraint's constant needs to be equal to the height of backgroundView's content. This way, our contentView won't cover up what we want to see from backgroundView. We should also save this height as backgroundViewContentHeight so that we can reference it later.
We also need contentView's leading, trailing, and bottom constraints to be equal to those of its superview, i.e. scrollView. These do not need to be connected via IBOutlets.
Also, give contentView a height and width constraint and connect these as contentViewHeight and contentViewWidth respectively to SquirtleViewController via IBOutlet. This will help us set the contentSize of our scrollView later.
Your code should now look something like this:
class SquirtleViewController: UIViewController {
#IBOutlet var backgroundView: UIView!
#IBOutlet var scrollView: UIScrollView!
#IBOutlet var contentView: UIView!
//New lines of code
//Our constraints
#IBOutlet var contentViewTop: NSLayoutConstraint!
#IBOutlet var contentViewHeight: NSLayoutConstraint!
#IBOutlet var contentViewWidth: NSLayoutConstraint!
var backgroundViewContentHeight = 400
override func viewDidLoad() {
super.viewDidLoad()
scrollView.delegate = self
}
}
extension SquirtleViewController: UIScrollViewDelegate {
}
Now, when SquirtleViewController lays out its subviews, we are going to want to set the constant properties of contentViewHeight and contentViewWidth. This will set a contentSize for our scrollView. We want our contentViewWidth to simply be the size of our SquirtleViewController's view's width. Our contentViewHeight's constant will be a little different though, as it depends on the height of our contentView's subviews. For this example, we'll say the contentViewHeight's constant should be 1200. The code should now look like this:
class SquirtleViewController: UIViewController {
#IBOutlet var backgroundView: UIView!
#IBOutlet var scrollView: UIScrollView!
#IBOutlet var contentView: UIView!
//Our constraints
#IBOutlet var contentViewTop: NSLayoutConstraint!
#IBOutlet var contentViewHeight: NSLayoutConstraint!
#IBOutlet var contentViewWidth: NSLayoutConstraint!
var backgroundViewContentHeight = 400
override func viewDidLoad() {
super.viewDidLoad()
scrollView.delegate = self
}
//New lines of code
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
contentViewWidth.constant = view.frame.width
contentViewHeight.constant = 1200
}
}
extension SquirtleViewController: UIScrollViewDelegate {
}
From here, you can add subviews to backgroundView and contentView as you see fit in order to show the pokemon (backgroundView) as well as the pokemon's stats (contentView). But, you still need to know how to change the content of your backgroundView depending upon how much we have scrolled the scrollView. This is where the UIScrollViewDelegate helps us out.
There is a method in UIScrollViewDelegate that is called every time our scrollView's contentOffset.y changes. This basically means every time we change the amount that our scrollView has scrolled, this method will be called.
Inside of this method, we can cross reference the amount we've scrolled with the height of the background view. As our scrollView's contentOffset.y approaches the height of our backgroundView's content, we can fade out the image of Squirtle and fade in a UILabel that simply says "Squirtle" (like in your example).
So, in your case, I would suggest adding a UIImage of Squirtle as a subview of contentView and connect it via IBOutlet as pokemonImageView. pokemonImageView should have its frame partially outside of the contentView (like in your example). If you do this, ensure that contentView's clipsToBounds property is set to false.
I would also add a UILabel as a subview of backgroundView and connect it to SquirtleViewController via IBOutlet as pokemonNameLabel. When our SquirtleViewController's view loads, we should set pokemonNameLabel.alpha equal to zero so that it is initially hidden.
Your code should now look like this:
class SquirtleViewController: UIViewController {
#IBOutlet var backgroundView: UIView!
//New line of code
#IBOutlet var pokemonNameLabel: UILabel!
#IBOutlet var scrollView: UIScrollView!
#IBOutlet var contentView: UIView!
//New line of code
#IBOutlet var pokemonImageView: UIImageView!
//Our constraints
#IBOutlet var contentViewTop: NSLayoutConstraint!
#IBOutlet var contentViewHeight: NSLayoutConstraint!
#IBOutlet var contentViewWidth: NSLayoutConstraint!
var backgroundViewContentHeight = 400
override func viewDidLoad() {
super.viewDidLoad()
scrollView.delegate = self
//New line of code
pokemonNameLabel.alpha = 0
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
contentViewWidth.constant = view.frame.width
contentViewHeight.constant = 1200
}
}
extension SquirtleViewController: UIScrollViewDelegate {
}
Now we simply need to add the scrollViewDidScroll method inside of our class extension. This is the method that will tell us our scrollView's contentOffset.y property so that we know how much we've scrolled. As this number increases, our pokemonImageView's alpha should decrease to zero and our pokemonNameLabel's alpha should increase towards one.
Inside of this method, I would divide our scrollView.contentOffset.y by the height of our backgroundView minus the height of the pokemonNameLabel. We can then use this decimal to set our respective alphas of pokemonImageView and pokemonNameLabel.
Your code should now look something like this:
class SquirtleViewController: UIViewController {
#IBOutlet var backgroundView: UIView!
//New line of code
#IBOutlet var pokemonNameLabel: UILabel!
#IBOutlet var scrollView: UIScrollView!
#IBOutlet var contentView: UIView!
//New line of code
#IBOutlet var pokemonImageView: UIImageView!
//Our constraints
#IBOutlet var contentViewTop: NSLayoutConstraint!
#IBOutlet var contentViewHeight: NSLayoutConstraint!
#IBOutlet var contentViewWidth: NSLayoutConstraint!
var backgroundViewContentHeight = 400
override func viewDidLoad() {
super.viewDidLoad()
scrollView.delegate = self
//New line of code
pokemonNameLabel.alpha = 0
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
contentViewWidth.constant = view.frame.width
contentViewHeight.constant = 1200
}
}
extension SquirtleViewController: UIScrollViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
//Subtracting contentView.frame.height from scrollView.frame.height
let alphaDecimal = scrollView.contentOffset.y / (backgroundViewContentHeight - pokemonNameLabel.frame.height)
pokemonNameLabel.alpha = alphaDecimal
pokemonImageView.alpha = 1 - alphaDecimal
}
}
Now, as we scroll up and what can be seen of the backgroundView shrinks, our pokemonImageView will fade out and our pokemonNameLabel will fade in. There will be a point in-between where both will have an alpha of 0.5. You can mess with this scrollViewDidScroll method as you see fit to make this work best for you.

How to add a custom view from a xib to a UIScrollView? [duplicate]

This question already has answers here:
Load view from XIB as a subview of a scrollview
(2 answers)
Closed 3 years ago.
Problem
I am trying to add a custom view from a xib into a UIScrollView.
I added a UIScrollView to an empty ViewController in my storyboard. I also added 4 constraints for leading, trailing, top, bottom to Safe Area and set them all Equal to 0.
Now in the code I want to add a custom view "EquipmentInfoView" to the existed UIScrollView
class EquipmentInfoVC: UIViewController {
#IBOutlet var scrollView: UIScrollView!
override func viewDidLoad() {
super.viewDidLoad()
equipmentInfoView = EquipmentInfoView.instantiate()
scrollView.addSubview(equipmentInfoView)
}
}
And it does not work as the ScrollView does not scroll. How should I do it instead?
Here is the custom view "EquipmentInfoView"
class EquipmentInfoView: UIView {
#IBOutlet var nameTextView: UITextView!
#IBOutlet var descriptionTextView: UITextView!
#IBOutlet var equipmentImageView: UIImageView!
static func instantiate() -> EquipmentInfoView {
let view: EquipmentInfoView = initFromNib()
return view
}
}
Extra Notes
I know this is not the normal way to create a custom view from xib. However, this way I dont have to set the File's Owner to "EquipmentInfoView", instead I set the View's Custom Class to "EquipmentInfoView".
(Setting File's Owner to "EquipmentInfoView" gives me EXC_BAD_ACCESS when I do unit testing)
For clarity, this is how I would add this view to a normal (non-scroll) view:
equipmentInfoView = EquipmentInfoView.instantiate()
mainView.addSubview(equipmentInfoView)
equipmentInfoView.frame = mainView.bounds
equipmentInfoView.autoresizingMask = [.flexibleHeight, .flexibleWidth]
You are probably missing the contentSize of your scrollView
class EquipmentInfoVC: UIViewController {
#IBOutlet var scrollView: UIScrollView!
override func viewDidLoad() {
super.viewDidLoad()
equipmentInfoView = EquipmentInfoView.instantiate()
scrollView.contentSize = equipmentInfoView.frame.size
scrollView.addSubview(equipmentInfoView)
}
}

UIButton inside table cell not changing attributes

I have a UIButton inside my cell together with an image and a text label. I manage to change the image and label programatically, but the UIButton does not seem to respond to anything except isHidden.
This is my code, the button that is not changing is followButton:
import UIKit
class ProfileTableCell: UITableViewCell {
#IBOutlet weak var name: UILabel!
#IBOutlet weak var profileImage: UIImageView!
#IBOutlet weak var followButton: UIButton!
override func awakeFromNib() {
super.awakeFromNib()
self.profileImage.layer.borderWidth = 0.0;
self.profileImage.layer.cornerRadius = self.profileImage.frame.size.width/2;
self.profileImage.clipsToBounds = true
self.profileImage.image = UIImage(named: "belt")
self.name.text = "Bar Refaeli"
self.followButton.layer.borderColor = UIColor.black.cgColor
self.followButton.layer.borderWidth = 3.0;
self.followButton.layer.cornerRadius = self.frame.size.width/4
self.followButton.backgroundColor = UIColor.black
}
func setCell(image: UIImage, name: String){
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
The profileImage and name outlets change the appearance fine, like mentioned above.
I also tried to remove the button and bring it back in, clean xcode project, remove the outlet reference and connecting it again. Pretty frustrated by now.
I also tried to change the background color of the button through the storyboard, just for testing, and it does not change it! what does change is the titleLabel and the text color.
awakeFromNib()- Prepares the receiver for service after it has been loaded from an Interface Builder archive, or nib file.
Given that, move your code to a view initiating method like viewDidLoad or viewDidAppear(_:)
Child objects that are attributes like textLabels act differently than child view objects.
Eventually I actually solved this by tossing the table view to the garbage and implementing the same needs using a collection view. there was no problem there..

Custom views in Horizontal scroll view. Is it possible?

I am designing an app which has a screen in which I have a horizontal scroll view which I fill with UIViews dynamically depending upon the number of data I have in my array . I did the same via programmatically. I have mentioned my approach below.
1) I put a Scroll view for scrolling horizontally and created a reference for that in my class.
2) I programatically added views as per my code -
var imagevieww = UIImageView()
#IBOutlet weak var hrzntlscrl: UIView!
#IBOutlet weak var scrollview: UIScrollView!
override func viewDidLoad()
{
super.viewDidLoad()
let viewcount = 15
for var i = 0; i < viewcount; i++
{
let viewnew = UIView(frame: CGRectMake( hrzntlscrl.frame.origin.x+110*CGFloat(i), 0, 100.0, hrzntlscrl.frame.height))
viewnew.backgroundColor = UIColor.orangeColor()
imagevieww = UIImageView(frame: CGRectMake(0, 10, 100.0, 50))
imagevieww.backgroundColor = UIColor.blackColor()
viewnew.addSubview(imagevieww)
scrollview.addSubview(viewnew)
}
}
So I just wanted to know that instead of creating a view and the corresponding subviews eg. here imageview and setting their location and frame size programatically , Can I have a standard custom view designed in my IB and use any reference of that in my for loop instead of creating one programmatically? If we can do that,can you please give me some steps.
Yes. This is possible. You can instantiate a class from a nib with
let customView: CustomView = NSBundle.mainBundle().loadNibNamed("CustomViewNibName", owner: self, options: nil)[safe: 0] as? CustomView
You would also need to set the content size of the horizontal scroll view to the combined width of all the views.
But I think that your use case would be better served by using a UICollectionView instad of a scroll view, UICollectionView does all this and more in a much simpler implementation.