Large titles with UITableView and a UIImage as a background - ios

I am making a financial app. I have a UINavigationController, wrapped around the UIViewController. The large titles in the navigation bar are turned on. I am using a UITableView as a scroller, and a UIImageView with a png image as a background. Now as I scroll the UITableView down, I expect the large titles shift to regular titles. By the way, my navigation bar is somewhat transparent. I am not even trying to ask you guys how to fix the transition from the large titles to regular titles in the navigation bar as I am sure that no one knows how to do that. At least none of the fixes that have been proposed here for 7 years could fix it for me. And as I learned here: https://www.reddit.com/r/iOSProgramming/comments/7k60dl/ios11_large_titles_not_shrinking_tip/, whoever is still trying to figure this problem out, should just abandon all hopes. IT IS IMPOSSIBLE TO PLACE UIIMAGEVIEWE BEHIND THE TABLE VIEW AND EXPECT THE LARGE TITLES TRANSITION TO WORK NORMALLY.
Therefore, I pinned the table to the master view and applied the background to the table directly. The transition started working, but very slowly and with hiccups. But that is not the problem. Since I have a transparent navigation bar, I want the background to occupy the entire screen, and the UITableView to be pinned to a safe area from the top, so nothing goes behind the navigation bar.
This is where the conundrum is. If I pin the background image to the table and make the table pinned to a safe area, whenever I scroll down, the large titles shrink, but the background image is stretching like Stretch Armstrong. It looks stupid. But If I pin the table view to the super view, the scrollable content shows through the navigation bar.
Question: How do I pin the table to a safe area, but make the background extend to super view, and have the large titles/regular titles transition still in place? So that the content doesn’t show behind the transparent navigation bar. Here’s my viewDidLoad:
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(masterTableViewAsViewScroller)
masterTableViewAsViewScroller.delegate = self
masterTableViewAsViewScroller.dataSource = self
let masterTableViewBackground = UIImageView(image: UIImage(named: "moneyBg"))
masterTableViewBackground.frame = self.masterTableViewAsViewScroller.frame
self.masterTableViewAsViewScroller.backgroundView = masterTableViewBackground;
masterTableViewAsViewScroller.pin(to: view)
title = "Some Title"
navigationController?.navigationBar.largeTitleTextAttributes = [NSAttributedString.Key.foregroundColor : UIColor.white]
navigationController?.navigationBar.titleTextAttributes = [NSAttributedString.Key.foregroundColor : UIColor.white]
let image = UIImage.imageFromColor(color: UIColor(red: 0/255, green: 0/255, blue: 0/255, alpha: 0.4))
self.navigationController?.navigationBar.setBackgroundImage(image, for: UIBarMetrics.default)
self.navigationController?.navigationBar.barStyle = .default
}
Also, my extensions, which are placed in a separate swift file:
import UIKit
extension UIView {
func pin(to superView: UIView) {
translatesAutoresizingMaskIntoConstraints = false
topAnchor.constraint(equalTo: superView.topAnchor).isActive = true
leadingAnchor.constraint(equalTo: superView.leadingAnchor).isActive = true
trailingAnchor.constraint(equalTo: superView.trailingAnchor).isActive = true
bottomAnchor.constraint(equalTo: superView.bottomAnchor).isActive = true
}
}
extension UIImage{
static func imageFromColor(color: UIColor) -> UIImage {
let rect = CGRect(x: 0, y: 0, width: 1, height: 1)
UIGraphicsBeginImageContextWithOptions(rect.size, false, 0)
color.setFill()
UIRectFill(rect)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image! } }
By the way, this is a redo for safe area pinning that I am using:
if #available(iOS 11, *) {
let guide = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
masterTableViewBackground.topAnchor.constraint(equalToSystemSpacingBelow: guide.topAnchor, multiplier: 1.0),
guide.bottomAnchor.constraint(equalToSystemSpacingBelow: masterTableViewBackground.bottomAnchor, multiplier: 1.0)
])
} else {
let standardSpacing: CGFloat = 8.0
NSLayoutConstraint.activate([
masterTableViewBackground.topAnchor.constraint(equalTo: topLayoutGuide.bottomAnchor, constant: standardSpacing),
bottomLayoutGuide.topAnchor.constraint(equalTo: masterTableViewBackground.bottomAnchor, constant: standardSpacing)
])
}
I add gifs so you could see what I'm talking about.

Related

Programatically creating constraints in a view isn't account for navigation controller despite using safeAreaLayoutGuide

I have created a UINavigationController class which allows users to Log out and displays the title of the app. I then added a UITabController as its only viewController in its viewControllers array:
let homeController = HomeController()
viewControllers = [homeController]
This UITabController (HomeController()) is then populated with a few UIViewControllers - one of which will display a Profile page. This is my first project in which I won't be using the storyboard so things have been a great challenge!
I have created a UIImageView object within the class and within my viewDidLoad for my profile page, I have used:self.view.addSubview(imageView)to add to view and then:imageView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor).isActive = true in an attempt to anchor the image to the bottom of the UINavigationController bar at the top of the screen.However the result places the image at the very top of the screen, as if the Navigation Bar isn't recognised as being visible. I read in this article: https://medium.com/#amlcurran/a-quick-guide-to-laying-out-views-in-ios-471e92deb74, that '.topLayoutGuide.bottomAnchor' represents the bottom of the navigation bar, but this has now been depreciated to my example above.
Does anyone have any ideas as to what is wrong?And also any good resources for me to fully understand programmatically constraining my elements!Thanks all!!
https://gist.github.com/JoeMcGeever/a5ce3be94fc49a8f27b1a2867bd9495b
That link shows some of the code so far - I am aware the other elements are also pinned to the top; I am just trying to fix this error regarding the navigation bar first.
Image showing what the view displays at the moment
You should really go through several auto-layout tutorials. There are many, many of them out there. After you've worked through a dozen or so, you should have a good idea of what needs to be done.
In the meantime, here is your ProfileViewController edited to give you an idea of what you were doing wrong:
class ProfileViewController : UIViewController {
let imageView : UIImageView = { //creates an image view with the name "imageView"
let image = UIImage(named: "logo")
let imageView = UIImageView(image: image)
return imageView
}()
let usernameLabel = UILabel()
// if you're addint a target referring to "self", this must be a lazy var
lazy var editProfileButton : UIButton = {
let editButton = UIButton()
editButton.backgroundColor = .orange
editButton.setTitle("Edit Profile", for: .normal)
editButton.setTitleColor(.white, for: .normal)
editButton.addTarget(self, action: #selector(handleEdit), for: .touchUpInside)
return editButton
}()
#objc func handleEdit(){
//edit handle button
print("Edit profile")
}
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.white
self.title = "Profile"
usernameLabel.text = "Username Here"
// we're going to use auto-layout
[imageView, usernameLabel, editProfileButton].forEach {
$0.translatesAutoresizingMaskIntoConstraints = false
}
self.view.addSubview(imageView)
self.view.addSubview(usernameLabel)
self.view.addSubview(editProfileButton)
// need FULL sets of constraints, not just TOP anchors
// respect safe-area
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
// image view at upper-left
// image view 8-pts from top of safe area
imageView.topAnchor.constraint(equalTo: g.topAnchor, constant: 8.0),
// and 8-pts from left
imageView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 8.0),
// give it a width of, for example, one-quarter the view width
imageView.widthAnchor.constraint(equalTo: g.widthAnchor, multiplier: 0.25),
// give it a 1:1 ratio (square)
imageView.heightAnchor.constraint(equalTo: imageView.widthAnchor),
// button at upper-right
editProfileButton.topAnchor.constraint(equalTo: g.topAnchor, constant: 8.0),
editProfileButton.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -8.0),
// no width or height... let the button size itself
// label below the image view
usernameLabel.topAnchor.constraint(equalTo: imageView.bottomAnchor, constant: 8.0),
usernameLabel.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 8.0),
// no width or height... let the label size itself
])
// give the name label a background color so we can see its frame
usernameLabel.backgroundColor = .cyan
}
}
Review the comments in the code to understand what I did.
Result will look about like this (I used a random image for the logo):
If you want to anchor your imageView to the top of safeArea, you have to constraint it to the topAnchor like this:
imageView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true

UIStackView height, content inside in the middle not at top

I have a UIStackview, and when its filled with a bunch of things, its fine, However, when its filled with 1, or 2 (for example), it spreads them out and not keeps them at top. What did I do wrong?
This is what happens. I want them to be at the top and with No right under it.
for i in 0..<self.itemSpecificsArray.count {
let itemSpecificName = UILabel()
let itemSpecificValue = UILabel()
itemSpecificName.textColor = UIColor(red: 236.0 / 255.0, green: 91.0 / 255.0, blue: 110.0 / 255.0, alpha: 1.0)
itemSpecificName.textAlignment = .left
itemSpecificName.font = UIFont.boldSystemFont(ofSize: 18.0)
itemSpecificValue.textColor = UIColor.black
itemSpecificValue.textAlignment = .left
itemSpecificValue.font = UIFont.systemFont(ofSize: 15.0)
print(self.itemSpecificsArray[i].itemSpecificName)
print("** \(self.itemSpecificsArray[i].itemSpecificValue)")
itemSpecificName.text = self.itemSpecificsArray[i].itemSpecificName
itemSpecificValue.text = self.itemSpecificsArray[i].itemSpecificValue
self.mainSectionItemSpecifics.addArrangedSubview(itemSpecificName)
self.mainSectionItemSpecifics.addArrangedSubview(itemSpecificValue)
if (i == self.itemSpecificsArray.count) {
let view = UIView()
self.mainSectionItemSpecifics.addArrangedSubview(view)
}
}
For each index, it has a itemSpecificName and itemSpecificValue. If I need to put these two Labels inside of a View, then I can do that...That will probably fix it, wouldnt it?
UIStackView wants to stretch its arranged subviews to fill its own (the stack view's) bounds. If you don't want the labels stretched, you either need to change the constraints on the UIStackView so that it can shrink to fit its children, or add an arranged subview to the stack view that can absorb the extract space. For example, you can add a plain UIView with no constraints after the two labels. Auto layout will give the extra space of the stack view's bounds to the UIView.

Setting Image and Text to Button with AutoLayout

I'm trying to create a button with a drop down arrow to the right of the text programatically like so:
The solutions I've seen have used title and image insets, but is there a way to set these with autoLayout programatically? Depending on the option selected, the text in the button could change and the text lengths will be different, so I'm not sure if title and edge insets are the way to go.
This is an example of where a UIStackView is placed in the main VC container view (in my case the UIStackView takes up all available space inside the VC). Basic user information is added in this case a telephone number.
I create a telephone number container view (UIView), a UILabel to contain the tel. no. and an UIImageView for the drop down arrow.
let telNoContainerView: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
let telNoLabel: UILabel = {
let view = UILabel()
let font = UIFont.systemFont(ofSize: 15)
view.font = font
view.backgroundColor = UIColor.white
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
let telNoImageView: UIImageView = {
let view = UIImageView()
view.backgroundColor = UIColor.white
view.tintColor = ACTION_COLOR
view.image = UIImage(named: "Chevron")?.withRenderingMode(.alwaysTemplate)
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
In setBasicInfoViews() simply add the telNoContainerView to he UIStackView. Then the UILabel and the UIImageView are added to the contain view telNoContainerView. Afterward the constraints are added as needed.
You will need to change the constraints to fit your UI design.
fileprivate func setBasicInfoViews(){
infoStackView.addArrangedSubview(telNoContainerView)
telNoContainerView.addSubview(telNoLabel)
telNoContainerView.addSubview(telNoImageView)
NSLayoutConstraint.activate([
telNoLabel.topAnchor.constraint(equalTo: telNoContainerView.topAnchor, constant: 0.0),
telNoLabel.bottomAnchor.constraint(equalTo: telNoContainerView.bottomAnchor, constant: 0.0),
telNoLabel.leadingAnchor.constraint(equalTo: telNoContainerView.leadingAnchor, constant: 0.0),
telNoLabel.trailingAnchor.constraint(equalTo: telNoContainerView.trailingAnchor, constant: 0.0)
])
NSLayoutConstraint.activate([
telNoImageView.centerYAnchor.constraint(equalTo: telNoLabel.centerYAnchor, constant: 0.0),
telNoImageView.heightAnchor.constraint(equalToConstant: 30.0),
telNoImageView.widthAnchor.constraint(equalToConstant: 30.0),
telNoImageView.trailingAnchor.constraint(equalTo: telNoLabel.trailingAnchor, constant: 0.0)
])
}
No, there isn't a way to set the image and title layout properties on a UIButton using AutoLayout.
If you want a fully custom layout for an Image and Title in a UIButton, I would suggest creating a UIView and add a title and an image as subviews using AutoLayout and then add a tap gesture recognizer to the UIView
buttonView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.buttonAction)))

Button in the footer of tableview section Swift3

I have a footer in the section of my tableView.
I try to add a button, in the center, and after to center my button.
I didn't find how I can center my button, I try to use view.center, or give him the width of all the parent view and after use a text align for center my button. (Personally I prefer use the second method .)
Actually my code is this one
override func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
let footerView = UIView()
let labelMore = UIButton()
footerView.addSubview(labelMore)
labelMore.sizeToFit()
labelMore.setTitle(footerText(status: "Test"), for: .normal)
labelMore.backgroundColor = #colorLiteral(red: 0.3411764801, green: 0.6235294342, blue: 0.1686274558, alpha: 1)
footerView.backgroundColor = #colorLiteral(red: 0.2392156869, green: 0.6745098233, blue: 0.9686274529, alpha: 1)
//labelMore.addTarget(self, action:#selector(footerMore(sender:)), for: .touchUpInside)
return footerView
}
And I got, the green square is actually my button, I erase from my code labelMore.frame = footerView.frame, because that don't work
edit: If my question is not clear I want center my button.
None of the code you showed "centers" the button, so it is not surprising that it is not centered. You do not set the frame.origin of labelMore at all, so its origin is zero — the top left corner, exactly as shown in the screen shot.
I erase from my code labelMore.frame = footerView.frame
Yes, well that was never going to work. It's just another case of confusing frame and bounds. Remember, the frame of the subview is in terms of the bounds of the superview. So you would change that code to
labelMore.frame = footerView.bounds
However, that isn't going to work either because footerView has no bounds — you haven't given it any size!
The real solution here is to position labelMore using autolayout. That way, no matter how footerView ends up being sized, labelMore will have the correct position within it. For example, let's assume that you want labelMore to be the same size as footerView:
let footerView = UIView()
let labelMore = UIButton()
footerView.addSubview(labelMore)
labelMore.topAnchor.constraint(equalTo: footerView.topAnchor).isActive = true
labelMore.bottomAnchor.constraint(equalTo: footerView.bottomAnchor).isActive = true
labelMore.leadingAnchor.constraint(equalTo: footerView.leadingAnchor).isActive = true
labelMore.trailingAnchor.constraint(equalTo: footerView.trailingAnchor).isActive = true
labelMore.translatesAutoresizingMaskIntoConstraints = false
// ... and remove the `sizeToFit` ...

Blur effect on navigation bar while using a tableviewcontroller

Real time blur effect for Navigation Bar
Tried the solution mentioned in the above post,
AppDelegate.swift
// Sets background to a blank/empty image
UINavigationBar.appearance().setBackgroundImage(UIImage(), forBarMetrics: .Default)
// Sets shadow (line below the bar) to a blank image
UINavigationBar.appearance().shadowImage = UIImage()
// Sets the translucent background color
UINavigationBar.appearance().backgroundColor = UIColor(red: 0.0, green: 0.0, blue: 0.0, alpha: 0.0)
// Set translucent. (Default value is already true, so this can be removed if desired.)
UINavigationBar.appearance().translucent = true
RootVC.swift
func addBlurEffect() {
// Add blur view
let bounds = self.navigationController?.navigationBar.bounds as CGRect!
let visualEffectView = UIVisualEffectView(effect: UIBlurEffect(style: .Light))
visualEffectView.frame = bounds
visualEffectView.autoresizingMask = [.FlexibleWidth, .FlexibleHeight]
self.navigationController?.navigationBar.addSubview(visualEffectView)
self.navigationController?.navigationBar.sendSubviewToBack(visualEffectView)
// Here you can add visual effects to any UIView control.
// Replace custom view with navigation bar in above code to add effects to custom view.
}
In viewDidLoad used self.addBlurEffect.
Problem, is status bar is still not blurred, and the blur effect is restricted only to RootVC.swift.
How can I extend it to all sub VCs?
Give this a shot:
bounds.offsetInPlace(dx: 0.0, dy: -20.0)
bounds.size.height = bounds.height + 20.0
Found it here

Resources