In swift, how do you add button using a for loop in stack view in scroll view? - swift

I have added two scroll views, and two vertical stack views inside them, respectively. The constraints have been set already. I have connect the two stack views as outlet collections in ViewController.swift. Suppose I have an array of strings, say
I wanted to add a few buttons (same as length of labels) in the top stack view using a for loop. How do I do this? And since the stack view is in scroll view, do I need to specify the distance between two buttons?
My code is below.
#IBOutlet var topStackView: [UIStackView]!
#IBOutlet var bottomStackView: [UIStackView]!
#IBOutlet weak var enterItemTextField: UITextField!
let labels = ["a", "b", "c", "d", "e"]
override func viewDidLoad() {
super.viewDidLoad()
enterItemTextField.delegate = self
var buttons = [UIButton()]
for _ in 0..<labels.count {
let button = UIButton(type: UIButton.ButtonType.system)
# some operations on button
buttons.append(button)
}
# what do I do here?
}
The stackview has less freedom to adjust the locations. I have removed the stackview and am putting buttons on the scroll view directly.

You need it after
buttons.append(button)
topStackView.addArrangedView(button)
BTW for single item you don't need a collection so to be
#IBOutlet var topStackView: UIStackView!
#IBOutlet var bottomStackView: UIStackView!
instead of
#IBOutlet var topStackView: [UIStackView]!
#IBOutlet var bottomStackView: [UIStackView]!
Do I need to specify the distance between two buttons?
no you don't have to but you can do this
topStackView.spacing = 20

Related

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 can I create a function for multiple labels in a stack?

I have 3 labels and I want to generate random numbers for each of them. I use GKRandomSource class in function, thats ok. The problem is, if I want to have much more labels (ie. 30) and all with same action, I need to reference all labels one by one to IBAction, I need to state all labels one by one in func code… I’ve been searching for a shorter way, maybe put them all in 3 stacks (10 labels for each stacks) and trigger, but I got nothing. I tried outlet collections (as we use in UIButtons) but it doesn’t let me to change label text.
How can I use a function for multiple labels with no-repeat?
Example;
let allNumbers = [Int](1...99)
var shuffledNum = [Int]()
#IBOutlet weak var labelOne: UILabel!
#IBOutlet weak var labelTwo: UILabel!
#IBOutlet weak var labelThree: UILabel!
func generateNumbers() {
shuffledNum = GKRandomSource.sharedRandom().arrayByShufflingObjects(in: allNumbers) as! [Int]
let threeNumbers = shuffledNum.prefix(3).sorted()
labelOne.text = String(threeNumbers[0])
labelTwo.text = String(threeNumbers[1])
labelThree.text = String(threeNumbers[2])
}
you can make the array of UILabel's and put all the outlets in the same array then you can use for loop to do operations on each of them.
for example:
#IBOutlet var formLabels: [UILabel]!
and can do as:
formLabels.forEach { label in
label.text = ""//put your random number function here
}
see its working after adding both outlets I have also shown the connection in the storyboard the connection exists

UIView's animate method not appearing

I am trying to make an app that utilizes some search feature and I am trying to make it so that after the search button is pressed, a view (which contains the search results) moves up from the bottom of the superview and replaces the search view. On the storyboard, the resultsView (of type UIView) is constrained so that its top is equal to the superview's bottom. After the search button is pressed, I would like to animate the view to move up and replace the view already at the bottom of the superview. The problem is, in the viewcontroller's class, when I call the resultsView, the animateWithDuration(NSTimeInterval) that is supposed to be associated with the UIView class is not appearing for me. May this be because the view is already constrained in place? Here is the code, simplified for this post:
import UIKit
import MapKit
class MapViewController: UIViewController, CLLocationManagerDelegate,
MKMapViewDelegate {
#IBOutlet weak var mapView: MKMapView!
#IBOutlet weak var slider: UISlider!
#IBOutlet weak var distanceLabel: UILabel!
#IBOutlet weak var searchButton: UIButton!
#IBOutlet weak var searchView: UIView!
#IBOutlet weak var resultView: UIView!
#IBOutlet weak var resultNameLabel: UILabel!
#IBOutlet weak var resultDistanceLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
mapView.delegate = self
self.resultView.isHidden = true
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func sliderAdjusted(_ sender: Any) {
let int = Int(slider.value)
switch int {
case 1:
distanceLabel.text = "1 mile"
default:
distanceLabel.text = "\(int) miles"
}
}
#IBAction func searchButtonPressed(_ sender: Any) {
/*This is where, after calling a search function which is omitted
from this example, I would like to make the resultsView not
hidden and then animate it to sort of eclipse the search view*/
self.resultView.isHidden = false
self.resultView.animate(withDuration: NSTimeInterval)
/*The above line of code is not actually appearing for me.
After typing "self.resultView." the animate function is not being
given to me as an option for my UIView*/
}
}
I will also attach some images of the view controller so you can sort of get the idea. The results view is not visible in this image because its top is constrained to the superview's bottom, thus it is just out of the visible representation of its superview.
The first image is the view controller with the searchView highlighted. This is the view that I would like to be eclipsed by my resultView after the searchButton is pressed.
The second image is the same view controller with the resultView highlighted. As you can see, its top is constrained to be equal to the superview's bottom. This is the view that I would like to animate upwards into the superview and eclipse the searchView after the search button is pressed.
The methods for all the animate family are all class methods. Which means you call them on the class object not an instance.
You are trying to call
class func animate(withDuration: TimeInterval, animations: () -> Void)
so your code needs to look like
UIView.animate(withDuration: 0.5) {
//the things you want to animate
//will animate with 0.5 seconds duration
}
In the particular case it looks like you are trying to animate the height of resultView so you need an IBOutlet to that constraint. You could call it resultViewHeight.
UIView.animate(withDuration: 0.5) {
self.resultViewHeight.constant = theDesiredHeight
self.layoutIfNeeded()
}
Calling layoutIfNeeded() within the closure is the secret sauce to animating auto layout. Without that the animation will just jump to the and point.

Swift: Generate Identifier for Variable

I have a large list of NSButtons (80+) that I assign to an array. I've used a numbering scheme to name them and I'd like to generate the identifiers for them instead of calling them directly by name one at a time.
#IBOutlet weak var button120: NSButton!
#IBOutlet weak var button121: NSButton!
var buttons [NSButton]()
Currently (in ViewDidLoad()) I'm doing the equivalent of:
buttons = [ button120, button121 ]
I'd prefer to do something like:
for index in 120...121 {
buttons.append("button\(index)".toIdentifier)
}
Can this be done in Swift?
This might be an easier solution, view is the parent view of the buttons
let buttons = view.subviews.filter { $0 is NSButton}

how add an object in game view controller?

#IBOutlet var green1: UIImageView!
#IBOutlet var red1: UIImageView!
#IBOutlet var blue1: UIImageView!
#IBAction func red(sender: UIButton) {
green1.hidden = true
red1.hidden = false
blue1.hidden = true
}
I want to add and remove objects when the button is pressed.
At the moment they hide and are visible, but i want to remove them completely form the scene when the button is pressed.
And i want the one that is not hidden to appear.
i tried add child and subview but it didn't work the code is in GameViewController.
If you are showing/hiding frequently, changing hidden property is ok, however if you want to remove completely you need to remove the particular image view from its super view.
ex - green1.removeFromSuperView() and when you need it, you have to add as subview and while adding provide its frame.