How to add Label in BWWalkthrough View Controller with Swift 3? - ios

I use BWWalkthrough library for slides images in my app. I add Title and Message labels in each slide.
I would like to translate to each labels.
So, I drag the label to IBOutlet and I add NStranslation text in ViewDidLoad.
But, when I run the code, I got fatal error. Here is my code.
In BWWalkthroughPageViewController.swift ,
#IBOutlet weak var lblTitle1: UILabel!
override open func viewDidLoad() {
super.viewDidLoad()
lblTitle1.text = NSLocalizedString("Date:", comment: "")
self.view.layer.masksToBounds = true
subviewsSpeed = Array()
for v in view.subviews{
speed.x += speedVariance.x
speed.y += speedVariance.y
if !notAnimatableViews.contains(v.tag) {
subviewsSpeed.append(speed)
}
}
}
I got error in these following codes (BWWalkthroughViewController.swift).
viewController.view.translatesAutoresizingMaskIntoConstraints = false
and lblTitle1.text = NSLocalizedString("Date:", comment: "")
Could anyone help me please?

Do something like below, that will add one label in all page, you can add more label like same way.
override open func viewDidLoad() {
super.viewDidLoad()
self.view.layer.masksToBounds = true
let sampleLabel:UILabel = UILabel()
sampleLabel.frame = CGRect(x: 100, y: 100, width: 200, height: 200)
sampleLabel.textAlignment = .center
sampleLabel.text = "Hello this is iOS dev"
sampleLabel.numberOfLines = 1
sampleLabel.textColor = .red
sampleLabel.font=UIFont.systemFont(ofSize: 22)
sampleLabel.backgroundColor = .yellow
view.addSubview(sampleLabel)
sampleLabel.translatesAutoresizingMaskIntoConstraints = false
sampleLabel.heightAnchor.constraint(equalToConstant: 200).isActive = true
sampleLabel.widthAnchor.constraint(equalToConstant: 200).isActive = true
sampleLabel.centerXAnchor.constraint(equalTo: sampleLabel.superview!.centerXAnchor).isActive = true
sampleLabel.centerYAnchor.constraint(equalTo: sampleLabel.superview!.centerYAnchor).isActive = true
subviewsSpeed = Array()
for v in view.subviews{
speed.x += speedVariance.x
speed.y += speedVariance.y
if !notAnimatableViews.contains(v.tag) {
subviewsSpeed.append(speed)
}
}
}
Update
You can prevent crash to happening by safe unwrapping lblTitle1 check below.
override open func viewDidLoad() {
super.viewDidLoad()
if (lblTitle1) != nil {
lblTitle1.text = NSLocalizedString("Date:", comment: "")
}
self.view.layer.masksToBounds = true
subviewsSpeed = Array()
for v in view.subviews{
speed.x += speedVariance.x
speed.y += speedVariance.y
if !notAnimatableViews.contains(v.tag) {
subviewsSpeed.append(speed)
}
}
}

Related

Reposition other UIButton after delete one

I have a code where the user can input several ingredients and can also delete them.
Method: input ingredient in searchBar then create UIButton for that input and add into UIView. I also have a function to arrange position of all buttons properly
class SearchViewController: UIViewController {
var listInputView = UIView()
let spacingX: CGFloat = 10
let spacingY: CGFloat = 10
let btnHeight: CGFloat = 30
var heightConstraint: NSLayoutConstraint = NSLayoutConstraint()
var listBtn = [UIButton]()
//and also initialization of other components like UISearchBar,...
override func viewDidLoad() {
super.viewDidLoad()
setupSegmentControl()
setupView()
listInputView.translatesAutoresizingMaskIntoConstraints = false
heightConstraint = listInputView.heightAnchor.constraint(equalToConstant: 10)
}
func setupSegmentControl() {
//setup UISegmentedControl
}
fileprivate func setupView() {
//setup UISearchBar
}
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
if segmentControl.selectedSegmentIndex == 0 {
//search from input menu
}
else {
//search from input ingredients
inputSearchBar.append(mainSearchBar.text!)
listIngredients()
}
}
func listIngredients() {
view.addSubview(listInputView)
listInputView.isHidden = false
NSLayoutConstraint.activate([
listInputView.topAnchor.constraint(equalTo: mainSearchBar.bottomAnchor, constant: 10),
listInputView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
listInputView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
heightConstraint
])
createIngredientBtn(ingredient: mainSearchBar.text!)
}
// create a button
func createIngredientBtn(ingredient: String) {
let deleteIcon = UIImage(systemName: "multiply")
let btn = UIButton(type: UIButton.ButtonType.custom) as UIButton
btn.setTitle(ingredient, for: UIControl.State.normal)
btn.setTitleColor(UIColor(hexString: "#707070"),for: UIControl.State.normal)
btn.layer.borderColor = UIColor(hexString: "#707070").cgColor
btn.layer.borderWidth = 1.0
btn.frame.size.height = btnHeight
btn.layer.masksToBounds = true
btn.contentEdgeInsets = UIEdgeInsets(top: 0, left: 8, bottom: 0, right: 8)
btn.setImage(deleteIcon, for: .normal)
btn.imageView?.contentMode = .scaleAspectFit
btn.imageEdgeInsets = UIEdgeInsets(top: 0, left: 10, bottom: 0, right: 3)
btn.semanticContentAttribute = UIApplication.shared.userInterfaceLayoutDirection == .rightToLeft ? .forceLeftToRight : .forceRightToLeft
btn.addTarget(self, action: #selector(deleteSelectedInput), for: UIControl.Event.touchDown)
btn.translatesAutoresizingMaskIntoConstraints = false
listBtn.append(btn)
listInputView.addSubview(btn)
}
// delete a button
#objc func deleteSelectedInput(_ sender: UIButton) {
sender.removeFromSuperview()
listBtn = listBtn.filter {$0 != sender}
showListBtn() //I try to call this here to reposition other buttons
viewDidLayoutSubviews()
inputSearchBar = inputSearchBar.filter {$0 != sender.currentTitle}
if inputSearchBar.isEmpty {
listInputView.isHidden = true
}
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
showListBtn()
}
//Arrange position of each button and update height of inputView according to them
func showListBtn() {
let listInputViewWidth = listInputView.frame.size.width
var currentOriginX: CGFloat = 0
var currentOriginY: CGFloat = 0
listBtn.forEach { btn in
// break to newline
if currentOriginX + btn.frame.width > listInputViewWidth {
currentOriginX = 0
currentOriginY += btnHeight + spacingY
}
// set the btn frame origin
btn.frame.origin.x = currentOriginX
btn.frame.origin.y = currentOriginY
// increment current X
currentOriginX += btn.frame.width + spacingX
}
// update listInputView height
heightConstraint.constant = currentOriginY + btnHeight
}
}
Problem: Suppose I have > 1 input ingredients (> 1 button) and I delete one of it, the rest will be overlapped on each other at the leftmost origin (where 1st button is placed). At this moment, the position of them is actually correct, they just don't re-display as they should be.
However, when I try to input new ingredient, they will then display properly.
Here is the image to clarify this better:
Input 3 ingredients: Ing1, Ing2, Ing3
Delete Ing1. Then Ing2 and Ing3 are overlapped at the position of Ing1.
Add new input Ing4. They are all position correctly again.
Why the buttons are not re-position correctly when I delete one of them and how can I solve this ? Please kindly help me. Thank you.
EDIT: I'm able to call viewDidLayoutSubviews after delete but the buttons still not re-draw at new position although the frame.origin has changed correctly.
#objc func deleteSelectedInput(_ sender: UIButton) {
sender.removeFromSuperview()
listBtn = listBtn.filter {$0 != sender}
listInputView.removeAllSubviews()
listBtn.forEach { btn in
listInputView.addSubview(btn)
}
inputSearchBar = inputSearchBar.filter {$0 != sender.currentTitle}
if inputSearchBar.isEmpty {
listInputView.isHidden = true
}
}

UIScrollView Reusable Views

I would like to create a UIScrollView that cycles between 3 paging views infinitely while I change the subviews to hold different reusable view controllers giving the illusion of infinitely many screen while only allocating the resources for 3 screens at a time similar to a UICollectionView.
Below is an attempt I've made at implementing such a view based on this answer Infinite UIScrollView however my project has not been working as expected.
Ive been trying to use 3 views as the basis for infinite scrolling and many labels as the subviews to be added and removed as the user scrolls.
import UIKit
class ViewController: UIViewController, UIScrollViewDelegate {
var scrollView:UIScrollView = {
let scrollView:UIScrollView = UIScrollView()
scrollView.backgroundColor = UIColor.orange
scrollView.translatesAutoresizingMaskIntoConstraints = false
return scrollView
}()
var labels:[CustomLabel] = [CustomLabel]()
var pages:[Page] = [Page]()
var visiblePages:Set<Page>!{
didSet{
print("visible pages count: \(recycledPages.count)")
}
}
var recycledPages:Set<Page>!{
didSet{
print("recycled pages count: \(recycledPages.count)")
}
}
var visibleLabels:Set<CustomLabel>!
var recycledLabels:Set<CustomLabel>!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
recycledPages = Set<Page>()
visiblePages = Set<Page>()
recycledLabels = Set<CustomLabel>()
visibleLabels = Set<CustomLabel>()
scrollView.contentSize = CGSize(width: view.bounds.width * 3, height: view.bounds.height)
scrollView.delegate = self
scrollView.isPagingEnabled = true
scrollView.indicatorStyle = .white
view.addSubview(scrollView)
NSLayoutConstraint.activate([
scrollView.topAnchor.constraint(equalTo: view.topAnchor),
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor)
])
configureLabels()
setUpPages()
for i in 0..<pages.count{
scrollView.addSubview(pages[i])
}
}
func setUpPages(){
let page1 = Page(),page2 = Page(),page3 = Page()
page1.backgroundColor = .red
page2.backgroundColor = .green
page3.backgroundColor = .blue
page1.index = 0
page2.index = 1
page3.index = 2
pages = [page1,page2,page3]
visiblePages = [page1,page2,page3]
setContentViewFrames()
}
func configureLabels(){
let label1 = CustomLabel(), label2 = CustomLabel(), label3 = CustomLabel()
label1.text = "label1"
label2.text = "label2"
label3.text = "label3"
label1.backgroundColor = .red
label2.backgroundColor = .green
label3.backgroundColor = .blue
labels = [label1,label2,label3]
setContentViewFrames()
}
// func dequeueRecycledPage()->CustomLabel?{
// let page = recycledPages.first
// if let page = page{
// recycledPages.remove(page)
// return page
// }
// return nil
// }
var currentIndex = 0
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if (scrollView.contentOffset.x < 0) {
let newOffset = CGPoint(x: scrollView.bounds.width + scrollView.contentOffset.x, y: 0)
scrollView.contentOffset.x = newOffset.x
rotateViewsRight()
}else if(scrollView.contentOffset.x > scrollView.bounds.size.width*2){
let newOffset = CGPoint(x: scrollView.contentOffset.x - scrollView.bounds.width, y: 0)
scrollView.contentOffset.x = newOffset.x
rotateViewsLeft()
}
print("current index: \(currentIndex)")
}
lazy var previousOffset:CGPoint = CGPoint()
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
print("currentIndex called")
if previousOffset.x < scrollView.contentOffset.x{
currentIndex += 1
}else if previousOffset.x > scrollView.contentOffset.x{
currentIndex -= 1
}
previousOffset = scrollView.contentOffset
}
func rotateViewsRight(){
let endView = pages.removeLast()
pages.insert(endView, at: 0)
setContentViewFrames()
}
func rotateViewsLeft(){
let endView = pages.removeFirst()
pages.append(endView)
setContentViewFrames()
}
func setContentViewFrames(){
for i in 0..<pages.count{
// let adjustedIndex = i % pages.count
// let view = pages[i]
// view.frame = CGRect(origin: CGPoint(x: view.bounds.width * CGFloat(i), y: 0), size: view.bounds.size)
pages[i].frame = CGRect(x: CGFloat(i)*view.bounds.width, y: 0, width: view.bounds.width, height: view.bounds.height)
let label = labels[currentIndex%labels.count]
label.text = "current label: \(currentIndex)"
label.frame = pages[i].frame
pages[i].addSubview(label)
}
}
func configurePage(forIndex index:Int){
}
func dequeueRecycledPage()->Page?{
let page = recycledPages.first
if let page = page{
recycledPages.remove(page)
return page
}
return nil
}
}

Not able to remove custom UIView from SuperView

This is extremely odd. I am trying to remove the view from superview when I drag the view to either left or right. If the view doesn't contain any subviews then I am easily able to remove the view from the superView by using this card.removeFromSuperview() - however, what I have noticed is that if add two views as subviews inside the card view then I am not able to remove it from superView and the entire thing goes bezerk.
Here is the card view class:
class MainSwipeCardView: UIView {
//MARK: - Properties
var swipeView = UIView()
var shadowView = UIView()
var text: String?
var label = UILabel()
var bgColor : UIColor? {
didSet {
swipeView.backgroundColor = bgColor
}
}
var cardsarraydata : CardDataModel? {
didSet {
bgColor = cardsarraydata?.backgroundColor
label.text = cardsarraydata?.title
}
}
var delegate : CardDelegate?
//MARK :- Init
override init(frame: CGRect) {
super.init(frame: .zero)
backgroundColor = .clear
configureShadowView()
configureSwipeView()
configureLabelView()
addPanGestureOnCards()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
//MARK: - Configurations
func configureShadowView() {
shadowView.backgroundColor = .clear
shadowView.layer.shadowColor = UIColor.black.cgColor
shadowView.layer.shadowOffset = CGSize(width: 0, height: 0)
shadowView.layer.shadowOpacity = 0.8
shadowView.layer.shadowRadius = 4.0
addSubview(shadowView)
shadowView.translatesAutoresizingMaskIntoConstraints = false
shadowView.leftAnchor.constraint(equalTo: leftAnchor).isActive = true
shadowView.rightAnchor.constraint(equalTo: rightAnchor).isActive = true
shadowView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
shadowView.topAnchor.constraint(equalTo: topAnchor).isActive = true
}
func configureSwipeView() {
swipeView.layer.cornerRadius = 15
swipeView.clipsToBounds = true
shadowView.addSubview(swipeView)
swipeView.translatesAutoresizingMaskIntoConstraints = false
swipeView.leftAnchor.constraint(equalTo: leftAnchor).isActive = true
swipeView.rightAnchor.constraint(equalTo: rightAnchor).isActive = true
swipeView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
swipeView.topAnchor.constraint(equalTo: topAnchor).isActive = true
}
func configureLabelView() {
swipeView.addSubview(label)
label.backgroundColor = .white
label.textColor = .black
label.textAlignment = .center
label.font = UIFont.systemFont(ofSize: 18)
label.translatesAutoresizingMaskIntoConstraints = false
label.leftAnchor.constraint(equalTo: leftAnchor).isActive = true
label.rightAnchor.constraint(equalTo: rightAnchor).isActive = true
label.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
label.heightAnchor.constraint(equalToConstant: 85).isActive = true
}
func addPanGestureOnCards() {
self.isUserInteractionEnabled = true
addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(handlePanGesture)))
}
//MARK: - Handlers
#objc func handlePanGesture(sender: UIPanGestureRecognizer){
let card = sender.view as! MainSwipeCardView
let point = sender.translation(in: self)
let centerOfParentContainer = CGPoint(x: self.frame.width / 2, y: self.frame.height / 2)
card.center = CGPoint(x: centerOfParentContainer.x + point.x, y: centerOfParentContainer.y + point.y)
switch sender.state {
case .ended:
if (card.center.x) > 400 {
delegate?.swipeDidEnd(on: card)
UIView.animate(withDuration: 0.2) {
card.center = CGPoint(x: centerOfParentContainer.x + point.x + 200, y: centerOfParentContainer.y + point.y + 75)
card.alpha = 0
self.layoutIfNeeded()
}
return
}else if card.center.x < -115 {
delegate?.swipeDidEnd(on: card)
UIView.animate(withDuration: 0.2) {
card.center = CGPoint(x: centerOfParentContainer.x + point.x - 200, y: centerOfParentContainer.y + point.y + 75)
card.alpha = 0
self.layoutIfNeeded()
}
return
}
UIView.animate(withDuration: 0.2) {
card.transform = .identity
card.center = CGPoint(x: self.frame.width / 2, y: self.frame.height / 2)
self.layoutIfNeeded()
}
default:
break
}
}
In this subclass I have two UIViews, I am adding the views sequentially. Then on swipeView I am adding the text and label and background color. This is how the cards look like:
I am also using a UIPanInteraction on it and so if I drag it to left or right, then I call the delegate which removes the entire MainSwipeCardView from the container view. In doing so this is what happens:
It keeps adding more and more in the background even though this is what I am calling in the delegate function:
func swipeDidEnd(on card: MainSwipeCardView) {
card.removeFromSuperview()
print(visibleCards.count)
}
The visibleCards is essentially an array of subviews in the container view. It should decrease so for example from 3 -> 2 -> 1; but it increases in non linear way ( not able to really get a relationship out of it)
The most confusing thing is that I am actually able to run this whole code just fine if I donot add the SwipeView and shadowView properties inside the custom view and just use the customView itself to house the label and the backgroundColor. When I add these two properties, then this whole thing seem to go haywire.
Please any kind of help will be extremely appreciated. Thanks!
ContainerView code is as follows:
class SwipeCardContainerView: UIView, CardDelegate {
//MARK: - Properties
var numberOfCards: Int = 0
var remainingCards: Int = 0
var cardsView : [MainSwipeCardView] = []
var numberOfAllowedCard: Int = 3
let horizontalInset: CGFloat = 8.0
let verticalInset: CGFloat = 8.0
var visibleCards : [MainSwipeCardView] {
return subviews as? [MainSwipeCardView] ?? []
}
var datasource : CardDataSource? {
didSet {
loadData()
}
}
override init(frame: CGRect) {
super.init(frame: .zero)
backgroundColor = .clear
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: - Configuration
func loadData() {
guard let datasource = datasource else { return }
numberOfCards = datasource.numberOfCards()
remainingCards = numberOfCards
for i in 0..<min(numberOfCards,numberOfAllowedCard) {
addCardView(at: i, card: datasource.createCard(at: i))
}
setNeedsLayout()
}
func addCardView(at index: Int, card: MainSwipeCardView) {
card.delegate = self
addCardFrame(index: index, cardView: card)
cardsView.append(card)
insertSubview(card, at: 0)
remainingCards -= 1
}
func addCardFrame(index: Int, cardView: MainSwipeCardView){
cardsView.append(cardView)
var cardViewFrame = bounds
let horizontalInset = (CGFloat(index) * self.horizontalInset)
let verticalInset = CGFloat(index) * self.verticalInset
cardViewFrame.size.width -= 2 * horizontalInset
cardViewFrame.origin.x += horizontalInset
cardViewFrame.origin.y += verticalInset
cardView.frame = cardViewFrame
}
// Delegate Method
func swipeDidEnd(on card: MainSwipeCardView) {
card.removeFromSuperview()
print(visibleCards.count)
}
Main ViewController Code:
class ViewController: UIViewController {
//MARK: - Properties
var stackContainer : SwipeCardContainerView!
var cardDataArray : [CardDataModel] = [CardDataModel(backgroundColor: .orange, title: "Hello"),
CardDataModel(backgroundColor: .red, title: "Red"),
CardDataModel(backgroundColor: .blue, title: "Blue"),
CardDataModel(backgroundColor: .orange, title: "Orange")]
//MARK: - Init
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor(red:0.93, green:0.93, blue:0.93, alpha:1.0)
stackContainer = SwipeCardContainerView()
view.addSubview(stackContainer)
configureSwipeContainerView()
stackContainer.translatesAutoresizingMaskIntoConstraints = false
}
//MARK : - Configurations
func configureSwipeContainerView() {
stackContainer.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
stackContainer.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: -50).isActive = true
stackContainer.widthAnchor.constraint(equalToConstant: 300).isActive = true
stackContainer.heightAnchor.constraint(equalToConstant: 350).isActive = true
}
override func viewDidLayoutSubviews() {
stackContainer.datasource = self
}
//MARK : - Handlers
}
extension ViewController : CardDataSource {
func numberOfCards() -> Int {
return cardDataArray.count
}
func createCard(at index: Int) -> MainSwipeCardView {
let card = MainSwipeCardView()
card.cardsarraydata = cardDataArray[index]
return card
}
func emptyCard() -> UIView? {
return nil
}
}
I've investigated the problem.
First issue is in the ViewController:
override func viewDidLayoutSubviews() {
stackContainer.datasource = self
}
Just remove this code. In each layout you set datasource... and loadData... this is incorrect approach, also super.viewDidLayoutSubviews() is missing...
And also stackContainer.datasource = self:
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor(red:0.93, green:0.93, blue:0.93, alpha:1.0)
stackContainer = SwipeCardContainerView()
view.addSubview(stackContainer)
configureSwipeContainerView()
stackContainer.translatesAutoresizingMaskIntoConstraints = false
stackContainer.datasource = self
Second issue is in func loadData(), just replace it with
func loadData() {
guard let datasource = datasource else { return }
setNeedsLayout()
layoutIfNeeded()
numberOfCards = datasource.numberOfCards()
remainingCards = numberOfCards
for i in 0..<min(numberOfCards,numberOfAllowedCard) {
addCardView(at: i, card: datasource.createCard(at: i))
}
}
or find better solution with layout of SwipeCardContainerView

Text Field cuts off first emoji when typing

I have a text field that sizes to the amount of text it has. It looks fine when you are not typing, but when you begin typing it shifts the emojis to the left and causes the first emoji to be cut off (it works fine with text).
I am not doing any manual resizing, just using sizeToFit().
Code:
override func viewDidLoad() {
super.viewDidLoad()
let emojiTextField = UITextField.makeEmojiTextField()
emojiTextField.delegate = self
view.addSubview(emojiTextField)
emojiTextField.anchorCenterSuperview()
}
class EmojiTextField: UITextField {
override var textInputMode: UITextInputMode? {
for mode in UITextInputMode.activeInputModes {
if mode.primaryLanguage == "emoji" {
return mode
}
}
return nil
}
}
extension UITextField {
static func makeEmojiTextField() -> EmojiTextField {
let tf = EmojiTextField()
tf.placeholder = ""
tf.font = UIFont.systemFont(ofSize: 64)
tf.borderStyle = UITextField.BorderStyle.none
tf.autocorrectionType = UITextAutocorrectionType.no
tf.returnKeyType = UIReturnKeyType.done
tf.contentVerticalAlignment = UIControl.ContentVerticalAlignment.center
tf.backgroundColor = .green
return tf
}
}
extension UIView {
public func anchorCenterXToSuperview(constant: CGFloat = 0) {
translatesAutoresizingMaskIntoConstraints = false
if let anchor = superview?.centerXAnchor {
centerXAnchor.constraint(equalTo: anchor, constant: constant).isActive = true
}
}
public func anchorCenterYToSuperview(constant: CGFloat = 0) {
translatesAutoresizingMaskIntoConstraints = false
if let anchor = superview?.centerYAnchor {
centerYAnchor.constraint(equalTo: anchor, constant: constant).isActive = true
}
}
public func anchorCenterSuperview() {
anchorCenterXToSuperview()
anchorCenterYToSuperview()
}
}
Emojis fit fine when not typing:
First emoji is cut off when typing:
Have you set any constraints to your text field?
if not try adding it to stack view and I believe it should work.

Paging UIScrollView seems to place content off centre

I have a UIScrollView that I want to have paging functionality (think an initial splash screen). I want that content (a UILabel and a UIImageView) to be placed centrally in each paging view on the scrollView. My problem is is that it is always slightly off centre ().
Here is the complete code:
var splashScreenObjects = [SplashScreenObject]()
var imageViewArray = [UIImageView]()
var subtitleViewArray = [UILabel]()
#IBOutlet var scrollView: UIScrollView!
#IBOutlet var pageControl: UIPageControl!
override func viewDidLoad() {
super.viewDidLoad()
createSplashScreenObjects()
configurePageControl()
configureScrollView()
}
func createSplashScreenObjects() {
let firstScreen: SplashScreenObject = SplashScreenObject(subtitle: "Medication reminders on your phone. Never miss your next dose", image: UIImage(named: "splashScreen1")!)
let secondScreen: SplashScreenObject = SplashScreenObject(subtitle: "Track how good you have been with your medication", image: UIImage(named: "splashScreen2")!)
let thirdScreen: SplashScreenObject = SplashScreenObject(subtitle: "The better you are with your medication, the more points you'll earn!", image: UIImage(named: "splashScreen3")!)
splashScreenObjects.append(firstScreen)
splashScreenObjects.append(secondScreen)
splashScreenObjects.append(thirdScreen)
}
func configureScrollView() {
self.scrollView.layoutIfNeeded()
self.scrollView.showsHorizontalScrollIndicator = false
self.scrollView.showsVerticalScrollIndicator = false
self.scrollView.pagingEnabled = true
self.scrollView.delegate = self
let width = view.frame.size.width
for index in 0..<splashScreenObjects.count {
let subtitle = UILabel(frame: CGRectMake((width * CGFloat(index)) + 25, self.scrollView.frame.size.height-75, width-50, 75))
subtitle.text = splashScreenObjects[index].subtitle
subtitle.textAlignment = NSTextAlignment.Center
subtitle.textColor = UIColor.whiteColor()
subtitle.font = UIFont(name:"Ubuntu", size: 16)
subtitle.numberOfLines = 2
subtitle.backgroundColor = UIColor.clearColor()
self.scrollView.addSubview(subtitle)
self.subtitleViewArray.append(subtitle)
subtitle.alpha = 0
let mainImage = UIImageView(frame: CGRectMake((width * CGFloat(index)), 50, width, self.scrollView.frame.size.height-150))
mainImage.image = splashScreenObjects[index].image
mainImage.contentMode = UIViewContentMode.ScaleAspectFit
self.scrollView.addSubview(mainImage)
self.imageViewArray.append(mainImage)
mainImage.alpha = 0
}
self.scrollView.contentSize = CGSizeMake(width * CGFloat(splashScreenObjects.count), self.scrollView.frame.size.height-50)
animateViews(Int(0))
}
func configurePageControl() {
self.pageControl.numberOfPages = splashScreenObjects.count
self.pageControl.currentPage = 0
self.view.addSubview(pageControl)
pageControl.addTarget(self, action: #selector(SplashViewController.changePage(_:)), forControlEvents: UIControlEvents.ValueChanged)
}
func changePage(sender: AnyObject) -> () {
let x = CGFloat(pageControl.currentPage) * self.view.frame.size.width
scrollView.setContentOffset(CGPointMake(x, 0), animated: true)
}
func scrollViewDidEndDecelerating(scrollView: UIScrollView) {
let pageNumber = round(scrollView.contentOffset.x / self.view.frame.size.width)
pageControl.currentPage = Int(pageNumber)
animateViews(Int(pageNumber))
}
func animateViews(pageNumber: Int) {
UIView.animateWithDuration(0.5, animations: {
self.imageViewArray[pageNumber].alpha = 1.0
self.subtitleViewArray[pageNumber].alpha = 1.0
})
}
Here are my auto layout constraints for the UIScrollView:
Your leading and trailing spaces are both -20, which means that the scroll view is 40 points wider than its superview. Change these to 0.
You should replace
self.scrollView.layoutIfNeeded()
to
self.view.layoutIfNeeded()
because layoutIfNeeded layout caller subviews, not itself. So, scrollView, when you add subtitle and mainImage on it, has wrong frame.

Resources