Customizing SLComposeSheetConfigurationItem - ios

I want to customize SLComposeSheetConfigurationItem
I am looking to change the font and color for its title and value
There does not seem to be any documentation for this but several apps like Evernote have done that.

SLComposeSheetConfigurationItem's contents are loaded in tableView, so you can't directly access both label. You need to first get tableView by accessing hierarchy of views and then access visibleCells from table view. there are only one cell, so access subViews of contentView of first cell which will give you two label.
So, first one label is left hand side and second one at right hand side. Change its font size, color, title as we are doing in normal label and reload data.
See following code.
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if let slSheet = self.children.first as? UINavigationController {
// Change color of Cancel and Post Button
slSheet.navigationBar.tintColor = .orange
// All contents of botton view are loaded inside table view at specific level of child view
if let tblView = slSheet.children.first?.view.subviews.first as? UITableView,
let firstCell = tblView.visibleCells.first
{
// Enumerate on subViews of contentView
for (index, label) in firstCell.contentView.subviews.enumerated() {
if index == 0 {
// Left label
if let lblLeft = label as? UILabel {
lblLeft.font = UIFont(name: "Helvetica-Bold", size: 20)
lblLeft.textColor = .orange
}
} else {
// Right label
if let lblRight = label as? UILabel {
lblRight.font = UIFont(name: "Helvetica", size: 14)
lblRight.textColor = .green
}
}
}
// Reload table if label not loading properly
tblView.reloadData()
}
}
}
Output:

Related

Simulating a badge on a UISegmentedControl that is being used as a navigation item

I have a UIViewController that is embedded in a UINavigationController. The navigation item for this view controller is a UISegmentedControl with 3 segments. I'm trying to find a way to add a "new" badge to each of the segments. It appears that UISegmentedControl does not normally allow you to do this but I was thinking that for my purposes, perhaps I could simulate this using a custom UIView positioned at the left or right edge of each segment. I know I can get the width of the UISegmentedControl and since the auto-size mode is set to "equal widths", it seems reasonable that I could simply divide the total width by 3 to determine the approximate width of each segment.
However, there are a couple of things that I'm not sure about:
Is it possible to determine the x/y position of the UISegmentedControl within the navigation bar so I know where to position the custom view(s)?
Is it then possible to add a custom view at these positions inside the space contained by the navigation bar?
Add Views above the Segment Control, put constraint.
In ViewDidLoad() use the code
self.badge1.addSubview(self.addCounter(count: 0))
self.badge2.addSubview(self.addCounter(count: 9))
Use this function to make badge counter
func addCounter(count: Int)->UIView {
// Count > 0, show count
if count > 0 {
// Create label
let fontSize: CGFloat = 10
let label = UILabel()
label.font = UIFont.systemFont(ofSize: fontSize)
label.textAlignment = .center
label.textColor = .white
label.backgroundColor = .red
// Add count to label and size to fit
label.text = "\(NSNumber(value: count))"
label.sizeToFit()
// Adjust frame to be square for single digits or elliptical for numbers > 9
var frame: CGRect = label.frame
frame.size.height += CGFloat(Int(0.4 * fontSize))
frame.size.width = (count <= 9) ? frame.size.height : frame.size.width + CGFloat(Int(fontSize))
label.frame = frame
// Set radius and clip to bounds
label.layer.cornerRadius = frame.size.height / 2.0
label.clipsToBounds = true
// Show label in accessory view and remove disclosure
return label
} else {
return UIView()
}
}
And final result
For those wanting to access the image views via the index, I created an extension, but it's not ideal, as it has to check whether the image data is equal, but works so far for me
extension UISegmentedControl {
func getImageView(at index: Int) -> UIImageView? {
guard let image = imageForSegment(at: index) else { return nil }
let imageViews = subviews.compactMap { $0.subviews.first(where: { $0 is UIImageView }) as? UIImageView }
return imageViews.first(where: {
return $0.image?.isEqualTo(image: image) == true
})
}
}
extension UIImage {
func isEqualTo(image: UIImage) -> Bool {
guard let data1 = image.pngData() else { return false }
guard let data2 = self.pngData() else { return false }
return data1 == data2
}
}

UITableViewCell doesn't change height when some UIStackView's subviews are unhidded

As the title says, I have a custom UITableCell in which I have some UIStackViews. Each of those stacks contains many subviews but I just want to show three of them when the cell is displayed for the first time. If a user wants to see more, there is a [+] button that calls a method that adds the remaining.
The custom cell height is determined via UITableViewAutomaticDimension and it works perfectly for the first display of the cell but when I try to add and remove subviews to the stack, there are views that shouldn't be modified that lose they constraints and the ones that should be displayed doesn't do it in some cases. What I'd like is to show all the UILabels and the height of the cell to be updated.
The method that is called when the button [+] is pressed is the following:
#objc private func changeImage(sender: UIButton) {
let index = (Int(sender.accessibilityValue!)!)
let open : Bool = openItem[index]
let plateStack : UIStackView = plateStacks[index]
let plates : [UILabel] = platesViews[index]
if !open {
sender.setImage(UIImage(named: "less")?.withRenderingMode(.alwaysTemplate), for: .normal)
let nPlatesToAdd = max(platesViews[index].count - 3, 0)
for i in 0..<nPlatesToAdd {
let plate = plates[i + 3]
plateStack.addArrangedSubview(plate)
plate.leadingAnchor.constraint(equalTo: plateStack.leadingAnchor, constant: 0).isActive = true
plate.trailingAnchor.constraint(equalTo: plateStack.trailingAnchor, constant: 0).isActive = true
}
}
else {
sender.setImage(UIImage(named: "more")?.withRenderingMode(.alwaysTemplate), for: .normal)
var i = plateStack.arrangedSubviews.count - 1
while i > 2 {
let view = plateStack.arrangedSubviews[i]
plateStack.removeArrangedSubview(view)
view.removeFromSuperview()
i = i - 1
}
}
openItem[index] = !open
}
The first display of the cell (everything's ok) and after click on the [+] button:
It happened because tableView is already rendered its layout.
You might need to check some causes :
make sure the stackView constraint is properly put to contentView
stackView's distribution must be fill
After you change something that affects tableView height, you can use these code to update cell height without reloading the table:
tableView.beginUpdates()
tableView.endUpdates()

Customize Segmented Control or ..?

Screenshot from twitter
I want this type of segmented control in Swift 4. I've researched Apple Documents but I couldn't find some of things which are I need such as removing borders, removing cornerRadius etc. How can I customize Segmented Control like Twitter's ? Or is there any another tab/segment solution ? Thanks.
Here is how to customize the UISegmentedControl to display a bottom border when it is selected:
Create a container view for the segmented control and pin it with Auto Layout to its super view.
Add a segmented control to the container view as a subview, and pin it with Auto Layout to the container view's edges.
Create a bottom underline view, add it as a subview to the container view, and apply Auto Layout to it (see example implementation).
Then set up an event listener: on the segmented control's value changed event, change the origin of the bottom underline view so that it is moved below the selected segment.
I also added some code on how to format the segmented control labels' font and text color, see it in the example below.
This is how it will look like:
Example implementation where the container and segmented control views are created programmatically:
Swift 4.2:
import UIKit
class ViewController: UIViewController {
private enum Constants {
static let segmentedControlHeight: CGFloat = 40
static let underlineViewColor: UIColor = .blue
static let underlineViewHeight: CGFloat = 2
}
// Container view of the segmented control
private lazy var segmentedControlContainerView: UIView = {
let containerView = UIView()
containerView.backgroundColor = .clear
containerView.translatesAutoresizingMaskIntoConstraints = false
return containerView
}()
// Customised segmented control
private lazy var segmentedControl: UISegmentedControl = {
let segmentedControl = UISegmentedControl()
// Remove background and divider colors
segmentedControl.backgroundColor = .clear
segmentedControl.tintColor = .clear
// Append segments
segmentedControl.insertSegment(withTitle: "First", at: 0, animated: true)
segmentedControl.insertSegment(withTitle: "Second", at: 1, animated: true)
segmentedControl.insertSegment(withTitle: "Third", at: 2, animated: true)
// Select first segment by default
segmentedControl.selectedSegmentIndex = 0
// Change text color and the font of the NOT selected (normal) segment
segmentedControl.setTitleTextAttributes([
NSAttributedStringKey.foregroundColor: UIColor.black,
NSAttributedStringKey.font: UIFont.systemFont(ofSize: 16, weight: .regular)], for: .normal)
// Change text color and the font of the selected segment
segmentedControl.setTitleTextAttributes([
NSAttributedStringKey.foregroundColor: UIColor.blue,
NSAttributedStringKey.font: UIFont.systemFont(ofSize: 16, weight: .bold)], for: .selected)
// Set up event handler to get notified when the selected segment changes
segmentedControl.addTarget(self, action: #selector(segmentedControlValueChanged), for: .valueChanged)
// Return false because we will set the constraints with Auto Layout
segmentedControl.translatesAutoresizingMaskIntoConstraints = false
return segmentedControl
}()
// The underline view below the segmented control
private lazy var bottomUnderlineView: UIView = {
let underlineView = UIView()
underlineView.backgroundColor = Constants.underlineViewColor
underlineView.translatesAutoresizingMaskIntoConstraints = false
return underlineView
}()
private lazy var leadingDistanceConstraint: NSLayoutConstraint = {
return bottomUnderlineView.leftAnchor.constraint(equalTo: segmentedControl.leftAnchor)
}()
override func viewDidLoad() {
super.viewDidLoad()
// Add subviews to the view hierarchy
// (both segmentedControl and bottomUnderlineView are subviews of the segmentedControlContainerView)
view.addSubview(segmentedControlContainerView)
segmentedControlContainerView.addSubview(segmentedControl)
segmentedControlContainerView.addSubview(bottomUnderlineView)
// Constrain the container view to the view controller
let safeLayoutGuide = self.view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
segmentedControlContainerView.topAnchor.constraint(equalTo: safeLayoutGuide.topAnchor),
segmentedControlContainerView.leadingAnchor.constraint(equalTo: safeLayoutGuide.leadingAnchor),
segmentedControlContainerView.widthAnchor.constraint(equalTo: safeLayoutGuide.widthAnchor),
segmentedControlContainerView.heightAnchor.constraint(equalToConstant: Constants.segmentedControlHeight)
])
// Constrain the segmented control to the container view
NSLayoutConstraint.activate([
segmentedControl.topAnchor.constraint(equalTo: segmentedControlContainerView.topAnchor),
segmentedControl.leadingAnchor.constraint(equalTo: segmentedControlContainerView.leadingAnchor),
segmentedControl.centerXAnchor.constraint(equalTo: segmentedControlContainerView.centerXAnchor),
segmentedControl.centerYAnchor.constraint(equalTo: segmentedControlContainerView.centerYAnchor)
])
// Constrain the underline view relative to the segmented control
NSLayoutConstraint.activate([
bottomUnderlineView.bottomAnchor.constraint(equalTo: segmentedControl.bottomAnchor),
bottomUnderlineView.heightAnchor.constraint(equalToConstant: Constants.underlineViewHeight),
leadingDistanceConstraint,
bottomUnderlineView.widthAnchor.constraint(equalTo: segmentedControl.widthAnchor, multiplier: 1 / CGFloat(segmentedControl.numberOfSegments))
])
}
#objc private func segmentedControlValueChanged(_ sender: UISegmentedControl) {
changeSegmentedControlLinePosition()
}
// Change position of the underline
private func changeSegmentedControlLinePosition() {
let segmentIndex = CGFloat(segmentedControl.selectedSegmentIndex)
let segmentWidth = segmentedControl.frame.width / CGFloat(segmentedControl.numberOfSegments)
let leadingDistance = segmentWidth * segmentIndex
UIView.animate(withDuration: 0.3, animations: { [weak self] in
self?.leadingDistanceConstraint.constant = leadingDistance
self?.layoutIfNeeded()
})
}
}
You may use part of CarbonKit. It has TabBar like you want. But it is necessary to code analyzing a little.
There is class CarbonTabSwipeSegmentedControl. There is property UIImageView indicator with tint color background (line 41) that draws upside or at bottom of string of segment. Also there is function updateIndicatorWithAnimation that resizes segment line. Also there is variable that helps draw and control this drawing.
I don't sure but you may simply include this class to your project and use it even in swift.

Iterate through all labels in a static table view

I have a static tableViews and a button to change the colour scheme (theme) of the app. Is there a fast way for me to change all the label colours throughout the table from white to black?
im thinking something like the following.
Pseudo code:
for subview in view.subviews {
if subview is UILabel {
subview.fontColor = .black
}
}
So when the orange switch is on the "Light" side I would like all labels to go black. I have used story board to construct it, so it would be nice if I could avoid having to connect all labels to the .swift file.
I am writing about code for your sudo code you need to iterate with recursion.
func getLabelsInView(view: UIView) -> [UILabel] {
var results = [UILabel]()
for subview in view.subviews as [UIView] {
if let label = subview as? UILabel {
results += [label]
} else {
results += getLabelsInView(view: subview)
}
}
return results
}
Call any where from you need to change color
let labels = getLabelsInView(self.view) // or any other view / table view
for label in labels {
label.textColor = .black
}

Make UIButton stick on bottom of UITableView

I have an UITableView which consists of prototype cells. I want to put an UIButton inside the bottom of the UITableView using Interface Builder.
I added the UIButton in the footer of the UITableView:
I added a purple background for the Footer View and a green background colour for the UITableView. In the picture above it shows the Button at the bottom of the footer. However this isn't equal to the bottom of the UITableView.
The GIF below displays that the button is placed bellow the cells but not inside the bottom of the UITableView. I want it to appear at the bottom in the UITableView. Not under the UITableView. The following GIF displays this problem:
My question is: How do I set an UIButton inside an UITableView at the bottom of the UITableView using Interface Builder?
This is what I want to achieve (From Apple's ResearchKit):
Edit: The UIButton should be inside the UITableView. Suggestions where the UIButton is placed outside the TableView and pinned underneath don't achieve my goal.
You are setting footer width wrong.Set it fixed height so that button sticks to that particular height(Should be Fixed like 60px)
Check Demo Code for Storyboard structure and constraints
So I had to slightly swizzle it, but got it working by doing the below things:
Pull the UIButton out to the same level in the view heirarcy as
the tableview.
Embed the tableview and the button inside a view
Embed the above view inside another view
Pin edges of view #3 (Pinned View) to superview
Pin top, left & right edges of view #2 (Resizing View) to view #3 edges. And set a constraint of equal height to view #3.
Set an outlet in the view controller for the equal height constraint
The view heirarcy in IB should look like this:
Now in the view controller code, you need to do the following things:
Create instance var for the keyboard offset value
var keyboardOffset: CGFloat = 0
set notifications and observers for the keyboard willShow and
willHide
notificationCenter.addObserver(self, selector: #selector(keyboardWillShow(_:)), name:NSNotification.Name.UIKeyboardWillShow, object: nil)
notificationCenter.addObserver(self, selector: #selector(keyboardWillHide(_:)), name:NSNotification.Name.UIKeyboardWillHide, object: nil)
In keyboardWillShow, cache the keyboard height value.
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue {
keyboardOffset = keyboardSize.height
}
Create didSet method on the keyboardOffset var, and animate the height of the view by that value each time it is set
var keyboardOffset: CGFloat = 0 {
didSet {
resizingViewHeight.constant = -keyboardOffset
UIView.animate(withDuration: 0.2) {
self.view.layoutIfNeeded()
}
}
}
Make sure you set the offset back to 0 in keyboardWillHide
keyboardOffset = 0
Every time the keyboard now appears, the view that is containing the tableview will reduce in size and therefore pull the contents up with it, providing the shrinking tableview effect that you are hoepfully looking for!
Add a view that contains the UIButton to the bottom of the UIViewController where the UITableView is. Give it the constraints to attach to left, right and bottom side of super view and probably a fixed height.
Then attach the UITableView's bottom constraint to the top of the view that contains the UIButton.
You should get the effect you're looking for.
NOTE: For the button you can give centered Y and X in superview constraints to keep it centered.
Footer is apperead always after the last cell of your table view so your output is correct.
If you wanted the button bottom of tableview then add button below the tableview in hierarchy not as a footer. But it makes your button static that means it didn't matter how much cells you have, button is always button of the tableView but it is not a scrollable like as it is now.
I tried the accepted answer, but couldn't get it to work. I found that the footer view always stayed pinned to the bottom of the screen, regardless of the size of the TableView (just as if it were a sibling of the TableView). I ended up following an approach suggested here: https://stackoverflow.com/a/18047772/5778751 The basic idea is that you programmatically determine the height of the TableView and depending on the result, you EITHER display a footer internal to the TableView OR display a view which is a sibling of the TableView.
I have a perfect solution for this problem. Using default was never that meaningful in my life.
The button under the view is also a table view cell from another section but its configuration of header height and interior design is just different from the above cells.
So I have five different sections. The first three of them are standard table view cells(SettingTableViewCell) but the last two(cache and version) are custom buttons. In the header title, I init for those empty titles.
enum Section: Int {
case adjustSettings
case about
case agreements
case cache
case version
static var numberOfSections: Int { return 5 }
var reuseIdentifier: String { return "SettingTableCell" }
var headerTitle: String? {
switch self {
case .adjustSettings: return "settings.adjust.section.title".localized
case .about: return "settings.headertitle.about".localized
case .agreements: return "agreement.title".localized
case .cache: return ""
case .version: return ""
}
}
Then I configured with cell will be in which section with below code. Cache and version have only one cell which will be our buttons.
var cells: [CellType] {
switch self {
case .adjustSettings:return [.notification,.language ]
case .about: return [.rate, .contact, .invite]
case .agreements: return [.membership, .kvkk, .illuminate]
case .cache: return [.cache]
case .version: return [.version]
}
}
I have three different set functions inside my settingsTableViewCell.
For setting up standard table view cell -> .setDefault(text: text)
For setting up my clean cache button -> .setCache(text: text)
Last for shoving version info -> .setVersion(version: version)
with the above cellForRowAt, I am switching rows and setting them up accordingly. My default is .setDefault
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let section = Section(rawValue: indexPath.section) else {
assertionFailure()
return UITableViewCell()
}
let row = section.cells[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: section.reuseIdentifier) as! SettingTableCell
switch row {
case .version:
cell.setVersion(version: getVersion())
case .cache:
ImageCache.default.calculateDiskCacheSize(completion: { size in
if size == 0 {
cell.setCache(text: "settings.clear.data".localized)
} else {
let byte = Int64(size)
let fileSizeWithUnit = ByteCountFormatter.string(fromByteCount: byte, countStyle: .file)
cell.setCache(text: "settings.cler.data.with.string".localized + "(\(String(describing: fileSizeWithUnit)))")
}
})
default:
cell.setDefault(text: row.text)
}
return cell
}
You can adjust button heights as below by switching section.
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
guard let section = Section(rawValue: indexPath.section) else { return 0 }
switch section {
case .cache: return 44
case .version: return 44
default: return 56.0
}
You can adjust the gap between each button as below.
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
guard let section = Section(rawValue: section) else { return 0 }
switch section {
case .adjustSettings: return 46
case .about: return 46
case .agreements: return 46
case .cache: return 9
case .version: return 0.5
default: return 46
}
And finally, this is my cell where I set .set functions to customize each cell as I pleased.
class SettingTableCell: UITableViewCell {
#IBOutlet weak var line: UIView!
#IBOutlet weak var content: UIView!
#IBOutlet weak var arrowView: UIView!
#IBOutlet weak var labelSetting: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
}
func setVersion(version: String) {
arrowView.isHidden = true
line.isHidden = true
content.backgroundColor = .clear
labelSetting.label(textStr: version, textColor: KSColor.neutral400.getColor(), textFont: .sfProTextRegular(size: 13), fontSize: 13, lineSpacing: -0.13, paragraphStyle: NSMutableParagraphStyle())
labelSetting.textAlignment = .center
self.accessoryType = .none
}
func setCache(text: String) {
arrowView.isHidden = true
line.isHidden = true
content.backgroundColor = KSColor.neutral100.getColor()
labelSetting.label(textStr: text, textColor: KSColor.neutral700.getColor(), textFont: .sfProTextMedium(size: 14), fontSize: 14, lineSpacing: -0.14, paragraphStyle: NSMutableParagraphStyle())
labelSetting.textAlignment = .center
self.accessoryType = .none
}
func setDefault(text: String) {
labelSetting.label(textStr: text, textColor: KSColor.neutral700.getColor(), textFont: UIFont.sfProTextMedium(size: 16), fontSize: 16, lineSpacing: -0.16, paragraphStyle: NSMutableParagraphStyle())
}
}
And the outcome is I have 5 sections but the last two are buttons.

Resources