NSTableView not displaying in NSView - swift

New to Swift, I am trying to implement an NSTableView in my SwiftUI macOS (>= 12.2) app. The table view is being added as a subview of an NSView, but the table view is not displaying on the screen.
Update 1:
A minimal reproducible example was added at the end of the post
The code was updated to conform to advice from #jnpdx
Note: If uncommenting the line //addSubview(tableView) in configureTableView(), (so that there is a duplicate addSubview(tableView) call) you can see a slim table at the very bottom
Update 2: It appears that both addSubview(tableView) lines may be needed for the tableView to be a descendant view of CustomView in the view hierarchy, and displayed within the bounds of the CustomView instance. It apparently would not create a separate instance. If that's the case, then the current issue is just the table's positioning at the bottom of the app view (which should be at the top) and ensuring the text box's visibility
Here is the implementation for my FilesView class, which is a subview of the CustomView class:
class FilesView: NSView {
let files: Files
#Published var selectedFile: AudioFile? = nil
#Published var sortDescending = false
var tableView = NSTableView()
var nameColumn = NSTableColumn(identifier: NSUserInterfaceItemIdentifier(rawValue: "name"))
var statusColumn = NSTableColumn(identifier: NSUserInterfaceItemIdentifier(rawValue: "status"))
var dateColumn = NSTableColumn(identifier: NSUserInterfaceItemIdentifier(rawValue: "date"))
var groupingColumn = NSTableColumn(identifier: NSUserInterfaceItemIdentifier(rawValue: "grouping"))
var durationColumn = NSTableColumn(identifier: NSUserInterfaceItemIdentifier(rawValue: "duration"))
init(files:Files){
self.files = files
super.init(frame: .zero)
configureTableView()
}
func addFile(file: AudioFile) {
self.files.files.append(file)
tableView.reloadData()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func configureTableView() {
print("in configureTableView()")
tableView.delegate = self
tableView.dataSource = self
tableView.identifier = NSUserInterfaceItemIdentifier(rawValue: "cell")
tableView.addTableColumn(nameColumn)
tableView.addTableColumn(statusColumn)
tableView.addTableColumn(dateColumn)
tableView.addTableColumn(groupingColumn)
tableView.addTableColumn(durationColumn)
tableView.headerView = nil
tableView.backgroundColor = .black
tableView.usesAlternatingRowBackgroundColors = true
tableView.gridStyleMask = .solidVerticalGridLineMask
tableView.focusRingType = .none
tableView.selectionHighlightStyle = .none
tableView.rowSizeStyle = .default
tableView.reloadData()
//addSubview(tableView) // tableView is a subview of FilesView and FilesView is a subview of CustomView, tableView will be added as a subview of CustomView when CustomView is created and tableView is passed as a parameter.
}
}
And here is the implementation for the CustomView class:
class CustomView: NSView {
private let tableView: FilesView
private let textView: TextBox
init(tableView: FilesView, textView: TextBox) {
self.tableView = tableView
self.textView = textView
super.init(frame: .zero)
addSubview(tableView)
addSubview(textView)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layout() {
super.layout()
let tableViewHeight = frame.height * 0.7
let textViewHeight = frame.height * 0.3
tableView.frame = NSRect(x: 0, y: 0, width: frame.width, height: tableViewHeight)
textView.frame = NSRect(x: 0, y: tableViewHeight, width: frame.width, height: textViewHeight)
}
}
The following invokes the CustomView in ContentView:
struct CustomViewRepresentable: NSViewRepresentable {
let filesView = FilesView(files: Files())
let textBox = TextBox()
func makeNSView(context: Context) -> NSView {
let customView = CustomView(tableView: filesView, textView: textBox)
return customView
}
func updateNSView(_ nsView: NSView, context: Context) {
// Update the NSView if necessary
}
typealias NSViewType = NSView
}
Here is the implementation in ContentView:
private var customViewRepresentable: CustomViewRepresentable
init(){
self.customViewRepresentable = CustomViewRepresentable()
}
In ContentView's body:
var body: some View {
VStack {
customViewRepresentable
}
}
Minimal reproducible example:
import SwiftUI
import Foundation
import Combine
class FilesView: NSView {
//let files: Files
#Published var sortDescending = false
var tableView = NSTableView()
var nameColumn = NSTableColumn(identifier: NSUserInterfaceItemIdentifier(rawValue: "name"))
var statusColumn = NSTableColumn(identifier: NSUserInterfaceItemIdentifier(rawValue: "status"))
var dateColumn = NSTableColumn(identifier: NSUserInterfaceItemIdentifier(rawValue: "date"))
var groupingColumn = NSTableColumn(identifier: NSUserInterfaceItemIdentifier(rawValue: "grouping"))
var durationColumn = NSTableColumn(identifier: NSUserInterfaceItemIdentifier(rawValue: "duration"))
init(){
super.init(frame: .zero)
configureTableView()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func configureTableView() {
print("in configureTableView()")
tableView.delegate = self
tableView.dataSource = self
tableView.identifier = NSUserInterfaceItemIdentifier(rawValue: "cell")
tableView.addTableColumn(nameColumn)
tableView.addTableColumn(statusColumn)
tableView.addTableColumn(dateColumn)
tableView.addTableColumn(groupingColumn)
tableView.addTableColumn(durationColumn)
tableView.headerView = nil
tableView.backgroundColor = .black
tableView.usesAlternatingRowBackgroundColors = true
tableView.gridStyleMask = .solidVerticalGridLineMask
tableView.focusRingType = .none
tableView.selectionHighlightStyle = .none
tableView.rowSizeStyle = .default
tableView.reloadData()
//addSubview(tableView) // tableView is a subview of FilesView and FilesView is a subview of CustomView, tableView will be added as a subview of CustomView when CustomView is created and tableView is passed as a parameter.
}
}
extension FilesView: NSTableViewDataSource {
func numberOfRows(in tableView: NSTableView) -> Int {
return 0
}
}
extension FilesView: NSTableViewDelegate {
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
guard let cell = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "cell"), owner: nil) as? NSTableCellView else {
return nil
}
if tableColumn == tableView.tableColumns[0] {
cell.textField?.stringValue = ""
} else if tableColumn == tableView.tableColumns[1] {
cell.textField?.stringValue = ""
}
else if tableColumn == tableView.tableColumns[2] {
cell.textField?.stringValue = ""
}
else if tableColumn == tableView.tableColumns[3] {
cell.textField?.stringValue = ""
}
else if tableColumn == tableView.tableColumns[4]{
cell.textField?.stringValue = ""
}
return cell
}
}
class TextBox: NSView {
#State private var selectedOption = 0 // 0 = strikeout, 1 = hide, 2 = underline
#State private var text = ""
#State private var isTextBoxHidden = false
var textEditor: some View {
TextEditor(text: $text)
.frame(minHeight: 200)
.padding()
.overlay(
RoundedRectangle(cornerRadius: 10)
.stroke(Color.gray, lineWidth: 2)
)
}
var exportButton: some View {
HStack {
Spacer()
Button(action: {
// code to export
}) {
Text("Export")
}
Spacer()
}
}
override func layout() {
super.layout()
// layout the views
}
}
class CustomView: NSView {
private let tableView: FilesView
private let textView: TextBox
init(tableView: FilesView, textView: TextBox) {
self.tableView = tableView
self.textView = textView
super.init(frame: .zero)
addSubview(tableView)
addSubview(textView)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layout() {
super.layout()
let tableViewHeight = frame.height * 0.7
let textViewHeight = frame.height * 0.3
tableView.frame = NSRect(x: 0, y: 0, width: frame.width, height: tableViewHeight)
textView.frame = NSRect(x: 0, y: tableViewHeight, width: frame.width, height: textViewHeight)
}
}
struct CustomViewRepresentable: NSViewRepresentable {
let filesView = FilesView()
let textBox = TextBox()
func makeNSView(context: Context) -> NSView {
let customView = CustomView(tableView: filesView, textView: textBox)
return customView
}
func updateNSView(_ nsView: NSView, context: Context) {
// Update the NSView if necessary
}
typealias NSViewType = NSView
}
//let customViewRepresentable = CustomViewRepresentable()
// init(){
// self.customViewRepresentable = CustomViewRepresentable()
// }
struct ContentView: View {
private var customViewRepresentable: CustomViewRepresentable
init(){
self.customViewRepresentable = CustomViewRepresentable()
}
// Conform to the ObservableObject protocol
var didChange = PassthroughSubject<Void, Never>()
var body: some View {
VStack {
customViewRepresentable
}
}
}

Related

UITapGestureRecognizer for UILabel inside of a StackView

I have a Stack View with two labels, one of which once tapped, suppose to lead to another view.
Here the code of my UIView subclass where labels and a StackView are setup:
import UIKit
import SnapKit
class WelcomeView: UIView {
weak var coordinator: MainCoordinator?
private let imageView: UIImageView = {
let imageView = UIImageView()
imageView.image = UIImage(named: "backGroundImage")
imageView.contentMode = .scaleAspectFill
imageView.layer.masksToBounds = true
return imageView
}()
private let questionLabel: UILabel = {
let questionLabel = UILabel()
questionLabel.text = "Don't have an Account?"
questionLabel.font = UIFont(name: "Avenir Next Regular", size: 17)
questionLabel.textColor = .black
return questionLabel
}()
private let signUpLabel: UILabel = {
let signUpLabel = UILabel()
let tap = UITapGestureRecognizer(target: self, action: #selector(ViewController.signUpTapped(tapGesture:)))
signUpLabel.text = "Sign Up"
signUpLabel.font = UIFont(name: "Avenir Next Regular", size: 17)
signUpLabel.textColor = .black
signUpLabel.highlightedTextColor = .link
signUpLabel.isHighlighted = true
signUpLabel.isUserInteractionEnabled = true
signUpLabel.addGestureRecognizer(tap)
return signUpLabel
}()
lazy var signUpstackView: UIStackView = {
let stackView = UIStackView(arrangedSubviews: [questionLabel, signUpLabel])
stackView.axis = .horizontal
stackView.alignment = .fill
stackView.distribution = .fill
stackView.spacing = 8
stackView.isUserInteractionEnabled = true
return stackView
}()
override init(frame: CGRect) {
super.init(frame: frame)
addSubViews()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
addSubViews()
}
func addSubViews() {
self.backgroundColor = .white
self.addSubview(imageView)
self.addSubview(btnSignIn)
self.addSubview(signUpstackView)
setConstraints()
}
func setConstraints() {
imageView.snp.makeConstraints { (make) in
make.edges.equalToSuperview()
}
btnSignIn.snp.makeConstraints { (make) in
make.height.equalTo(60)
make.bottomMargin.equalTo(-50)
make.leftMargin.equalTo(28)
make.rightMargin.equalTo(-28)
}
signUpstackView.snp.makeConstraints { (make) in
make.height.equalTo(24)
make.centerX.equalTo(self)
make.top.equalTo(btnSignIn).offset(70)
}
}
}
I added UITapGestureRecognizer in signUpLabel.
And here is the code from my ViewController containing my IBAction function signUpTapped which is specified in UITapGestureRecognizer:
class ViewController: UIViewController {
var welcomeView = WelcomeView()
override func loadView() {
view = welcomeView
}
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func signUpTapped(tapGesture:UITapGestureRecognizer) {
print("Tapped")
}
}
For some reason nothing is happened when I try to click on my SignUp Label. Is this an issue because my UILabel is inside of a StackView?
You need
Sol 1
let tap = UITapGestureRecognizer(target: self, action: #selector(self.signUpTapped(tapGesture:)))
then inside the view class
weak var delegate:ViewController?
#objc func signUpTapped(tapGesture:UITapGestureRecognizer) {
print("Tapped")
delegate?.tapped()
}
var welcomeView = WelcomeView()
override func loadView() {
welcomeView.delegate = self
view = welcomeView
}
func tapped(){}
Sol 2
weak var delegate:ViewController?
init(frame: CGRect,delegate:ViewController) {
super.init(frame: frame)
self.delegate = delegate
addSubViews()
}
With
let tap = delegate(target:delegate!, action: #selector(delegate!.signUpTapped(tapGesture:)))
and inside the vc
#objc func signUpTapped(tapGesture:UITapGestureRecognizer) {
print("Tapped")
}

TextField rejected resignFirstResponder when being removed from hierarchy

I'm trying to make a custom textfield/sendbutton view that overrides inputAccessoryView, but I'm having 2 problems I can't seem to solve:
When the custom view becomes the first responder, I get this warning twice:
CustomKeyboardProject[5958:3107074] API error:
<_UIKBCompatInputView: 0x119e2bd70; frame = (0 0; 0 0);
layer = <CALayer: 0x283df9e00>> returned 0 width,
assuming UIViewNoIntrinsicMetric
Then when I try to resign the custom view as the first responder, Xcode throws this warning:
CustomKeyboardProject[5958:3107074] -[UIWindow
endDisablingInterfaceAutorotationAnimated:] called on <UITextEffectsWindow:
0x11a055400; frame = (0 0; 375 667); opaque = NO; autoresize = W+H; layer =
<UIWindowLayer: 0x283df78a0>> without matching
-beginDisablingInterfaceAutorotation. Ignoring.
Has anyone been able to silence these warnings?
Heres my code:
protocol CustomTextFieldDelegate: class {
func sendMessage()
}
class CustomTextField: UITextField {
override var canBecomeFirstResponder: Bool {
return true
}
override var canResignFirstResponder: Bool {
return true
}
}
class CustomKeyboardView: UIView {
let textField: CustomTextField = {
let textField = CustomTextField()
textField.backgroundColor = .green
textField.textColor = .white
return textField
}()
let attachButton: UIButton = {
let button = UIButton()
button.backgroundColor = .red
button.setTitle("Attach", for: .normal)
return button
}()
let sendButton: UIButton = {
let button = UIButton()
button.backgroundColor = .blue
button.setTitle("Send", for: .normal)
button.addTarget(self, action: #selector(sendMessage), for: .touchUpInside)
return button
}()
#objc func sendMessage() {
delegate?.sendMessage()
}
weak var delegate: CustomTextFieldDelegate?
init() {
super.init(frame: .zero)
isUserInteractionEnabled = true
setViews()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setViews() {
let subviews = [attachButton, sendButton, textField]
subviews.forEach {
$0.translatesAutoresizingMaskIntoConstraints = false
addSubview($0)
}
autoresizingMask = .flexibleHeight
subviews.forEach {
NSLayoutConstraint.activate([
$0.centerYAnchor.constraint(equalTo: centerYAnchor),
])
}
layoutSubviews()
[attachButton, sendButton].forEach {
NSLayoutConstraint.activate([
$0.widthAnchor.constraint(equalTo: $0.heightAnchor),
$0.heightAnchor.constraint(equalTo: textField.heightAnchor)
])
}
NSLayoutConstraint.activate([
textField.topAnchor.constraint(equalTo: topAnchor),
attachButton.leftAnchor.constraint(equalTo: leftAnchor),
textField.leadingAnchor.constraint(equalTo: attachButton.trailingAnchor),
sendButton.leadingAnchor.constraint(equalTo: textField.trailingAnchor),
sendButton.trailingAnchor.constraint(equalTo: trailingAnchor),
])
}
override var intrinsicContentSize: CGSize {
return .zero
}
}
class CustomTableView: UITableView {
let keyboardView = CustomKeyboardView()
override init(frame: CGRect, style: UITableView.Style) {
super.init(frame: frame, style: style)
keyboardDismissMode = .interactive
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override var canBecomeFirstResponder: Bool {
return true
}
override var canResignFirstResponder: Bool {
return true
}
override var inputAccessoryView: UIView? {
return keyboardView
}
}
private let reuseId = "MessageCellId"
class ExampleViewController: UITableViewController {
private let customTableView = CustomTableView()
var messages = [String]()
override func loadView() {
tableView = customTableView
view = tableView
}
override func viewDidLoad() {
super.viewDidLoad()
tableView.becomeFirstResponder()
tableView.register(UITableViewCell.self, forCellReuseIdentifier: reuseId)
customTableView.keyboardView.delegate = self
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
DispatchQueue.main.async {
self.customTableView.keyboardView.textField.becomeFirstResponder()
}
}
// tableView delegate methods
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return messages.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: reuseId)
cell?.textLabel?.text = messages[indexPath.row]
return cell!
}
override func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
customTableView.keyboardView.textField.resignFirstResponder()
}
}
extension ExampleViewController: CustomTextFieldDelegate {
func sendMessage() {
// double check something is in textField
let textField = customTableView.keyboardView.textField
guard let body = textField.text, body != "" else { return }
messages.append(body)
tableView.reloadData()
view.endEditing(true)
customTableView.keyboardView.textField.text = ""
}
}

Swift - Size of content view does not match with UITableViewCell

I have an iOS app with a TableView, all UI of the app are done programmingly with autolayout using Swift.
All UI are working great until recently, I have to add a new component (custom UIView) inside the UITableViewCell which cell height will be changed when the new component is shown or hidden. The height of the cell is not correct so my views inside the UITableViewCell become a mess.
After checking the Debug View Hierarchy, I found that the height of the UITableViewCell is different than then UITableViewCellContentView.
When the component should display:
Table view cell has correct height (Longer height)
Content View of UITableViewCell is shorter then expected (the height is correct if component is hidden)
When the component should hidden:
Table view cell has correct height (Shorter height)
Content View of UITableViewCell is longer then expected (the height is correct if component is display)
I am not really sure what is the real issue. When I toggle the component to be displayed or not, I do the followings:
// Update the constraints status
var componentIsShown: Bool = .....
xxxConstraints?.isActive = componentIsShown
yyyConstraints?.isActive = !componentIsShown
// Update UI
layoutIfNeeded()
view.setNeedsUpdateConstraints()
tableView.beginUpdates()
tableView.endUpdates()
It seems to me that when I toggle the component to be displayed or not, the UITableViewCell is updated immediately, but content view used previous data to update the height. If this is the issue, how could I update the content view height also?
If this is not the issue, any suggestion to fix it or do further investigation?
Thanks
====================
Updated in 2018-08-29:
Attached are the codes for the issue.
Clicking on the topMainContainerView in MyBaseView (the view with red alpha bg) will toggle the hiddenView display or not.
In ViewController.swift:
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat
{
return UITableViewAutomaticDimension
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
var cell: MyViewCell
if let theCell = tableView.dequeueReusableCell(withIdentifier: "cell") as? MyViewCell
{
cell = theCell
}
else
{
cell = MyViewCell(reuseIdentifier: "cell")
}
cell.setViewCellDelegate(delegate: self)
return cell
}
MyViewCell.swift
class MyViewCell: MyViewParentCell
{
var customView: MyView
required init?(coder aDecoder: NSCoder)
{
return nil
}
override init(reuseIdentifier: String?)
{
customView = MyView()
super.init(reuseIdentifier: reuseIdentifier)
selectionStyle = .none
}
override func initViews()
{
contentView.addSubview(customView)
}
override func initLayout()
{
customView.translatesAutoresizingMaskIntoConstraints = false
customView.topAnchor.constraint(equalTo: contentView.topAnchor).isActive = true
customView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).isActive = true
customView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor).isActive = true
customView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor).isActive = true
}
override func setViewCellDelegate(delegate: MyViewCellDelegate)
{
super.setViewCellDelegate(delegate: delegate)
customView.delegate = delegate
customView.innerDelegate = self
}
}
MyViewParentCell.swift:
protocol MyViewCellDelegate
{
func reloadTableView()
}
protocol MyViewCellInnerDelegate
{
func viewCellLayoutIfNeeded()
}
class MyViewParentCell: UITableViewCell
{
private var delegate: MyViewCellDelegate?
var innerDelegate: MyViewCellInnerDelegate?
required init?(coder aDecoder: NSCoder)
{
return nil
}
init(reuseIdentifier: String?)
{
super.init(style: UITableViewCellStyle.default, reuseIdentifier: reuseIdentifier)
selectionStyle = .none
initViews()
initLayout()
}
func initViews()
{
}
func initLayout()
{
}
override func layoutSubviews()
{
}
func setViewCellDelegate(delegate: MyViewCellDelegate)
{
self.delegate = delegate
}
}
extension MyViewParentCell: MyViewCellInnerDelegate
{
func viewCellLayoutIfNeeded()
{
print("MyViewParentCell viewCellLayoutIfNeeded")
setNeedsUpdateConstraints()
layoutIfNeeded()
}
}
MyView.swift
class MyView: MyParentView
{
private var mainView: UIView
// Variables
var isViewShow = true
// Constraint
private var mainViewHeightConstraint: NSLayoutConstraint?
private var hiddenViewHeightConstraint: NSLayoutConstraint?
private var hiddenViewPosYHideViewConstraint: NSLayoutConstraint?
private var hiddenViewPosYShowViewConstraint: NSLayoutConstraint?
// Constant:
let viewSize = UIScreen.main.bounds.width
// Init
override init()
{
mainView = UIView(frame: CGRect(x: 0, y: 0, width: viewSize, height: viewSize))
super.init()
}
required init?(coder aDecoder: NSCoder)
{
return nil
}
override func initViews()
{
super.initViews()
topMainContainerView.addSubview(mainView)
}
override func initLayout()
{
super.initLayout()
//
mainView.translatesAutoresizingMaskIntoConstraints = false
mainView.topAnchor.constraint(equalTo: topMainContainerView.topAnchor).isActive = true
mainView.bottomAnchor.constraint(equalTo: topMainContainerView.bottomAnchor).isActive = true
mainView.leadingAnchor.constraint(equalTo: topMainContainerView.leadingAnchor).isActive = true
mainView.widthAnchor.constraint(equalToConstant: viewSize).isActive = true
mainView.heightAnchor.constraint(equalToConstant: viewSize).isActive = true
mainViewHeightConstraint = mainView.heightAnchor.constraint(equalToConstant: viewSize)
mainViewHeightConstraint?.isActive = true
}
override func toggle()
{
isViewShow = !isViewShow
print("toggle: isViewShow is now (\(isViewShow))")
setViewHidden()
}
private func setViewHidden()
{
UIView.animate(withDuration: 0.5) {
if self.isViewShow
{
self.hiddenViewBottomConstraint?.isActive = false
self.hiddenViewTopConstraint?.isActive = true
}
else
{
self.hiddenViewTopConstraint?.isActive = false
self.hiddenViewBottomConstraint?.isActive = true
}
self.layoutIfNeeded()
self.needsUpdateConstraints()
self.innerDelegate?.viewCellLayoutIfNeeded()
self.delegate?.reloadTableView()
}
}
}
MyParentView.swift
class MyParentView: MyBaseView
{
var delegate: MyViewCellDelegate?
var innerDelegate: MyViewCellInnerDelegate?
override init()
{
super.init()
}
required init?(coder aDecoder: NSCoder)
{
return nil
}
override func initViews()
{
super.initViews()
}
override func initLayout()
{
super.initLayout()
translatesAutoresizingMaskIntoConstraints = false
}
}
MyBaseView.swift
class MyBaseView: UIView
{
var topMainContainerView: UIView
var hiddenView: UIView
var bottomActionContainerView: UIView
var bottomSeparator: UIView
var hiddenViewTopConstraint: NSLayoutConstraint?
var hiddenViewBottomConstraint: NSLayoutConstraint?
// Layout constratint
var descriptionWidthConstraint: NSLayoutConstraint?
var moreMainTopAnchorConstraint: NSLayoutConstraint?
var moreMainBottomAnchorConstraint: NSLayoutConstraint?
var separatorTopAnchorToActionBarConstraint: NSLayoutConstraint?
var separatorTopAnchorToPartialCommentConstraint: NSLayoutConstraint?
// Constant
let paddingX: CGFloat = 10
let InnerPaddingY: CGFloat = 9
init()
{
topMainContainerView = UIView()
hiddenView = UIView()
bottomActionContainerView = UIView()
bottomSeparator = UIView()
super.init(frame: .zero)
initViews()
initLayout()
}
required init?(coder aDecoder: NSCoder)
{
return nil
}
func initViews()
{
let borderColor = UIColor.gray
backgroundColor = UIColor(red: 211/255.0, green: 211/255.0, blue: 1, alpha: 1)
topMainContainerView.backgroundColor = UIColor.red.withAlphaComponent(0.7)
let gesture = UITapGestureRecognizer(target: self, action: #selector(toggle))
topMainContainerView.addGestureRecognizer(gesture)
// Hidden View
hiddenView.backgroundColor = UIColor.yellow
hiddenView.layer.cornerRadius = 50
// Action
bottomActionContainerView.backgroundColor = UIColor.blue
bottomSeparator.backgroundColor = borderColor
// Add hiddenView first, so it will hide behind main view
addSubview(hiddenView)
addSubview(topMainContainerView)
addSubview(bottomActionContainerView)
addSubview(bottomSeparator)
}
func initLayout()
{
// MARK: Main
topMainContainerView.translatesAutoresizingMaskIntoConstraints = false
topMainContainerView.topAnchor.constraint(equalTo: topAnchor, constant: 30).isActive = true
topMainContainerView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20).isActive = true
topMainContainerView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -20).isActive = true
topMainContainerView.heightAnchor.constraint(equalToConstant: 150).isActive = true
// Hidden View
hiddenView.translatesAutoresizingMaskIntoConstraints = false
hiddenViewTopConstraint = hiddenView.topAnchor.constraint(equalTo: topMainContainerView.bottomAnchor)
hiddenViewTopConstraint?.isActive = true
hiddenView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20).isActive = true
hiddenView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -20).isActive = true
hiddenViewBottomConstraint = hiddenView.bottomAnchor.constraint(equalTo: topMainContainerView.bottomAnchor)
hiddenViewBottomConstraint?.isActive = false
hiddenView.heightAnchor.constraint(equalToConstant: 100).isActive = true
// MARK: Bottom
bottomActionContainerView.translatesAutoresizingMaskIntoConstraints = false
bottomActionContainerView.topAnchor.constraint(equalTo: hiddenView.bottomAnchor, constant: InnerPaddingY).isActive = true
bottomActionContainerView.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
bottomActionContainerView.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
bottomActionContainerView.heightAnchor.constraint(equalToConstant: 32).isActive = true
// MARK: Separator
bottomSeparator.translatesAutoresizingMaskIntoConstraints = false
separatorTopAnchorToPartialCommentConstraint = bottomSeparator.topAnchor.constraint(equalTo: bottomActionContainerView.bottomAnchor, constant: InnerPaddingY)
separatorTopAnchorToActionBarConstraint = bottomSeparator.topAnchor.constraint(equalTo: bottomActionContainerView.bottomAnchor, constant: InnerPaddingY)
separatorTopAnchorToPartialCommentConstraint?.isActive = false
separatorTopAnchorToActionBarConstraint?.isActive = true
bottomSeparator.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
bottomSeparator.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
bottomSeparator.heightAnchor.constraint(equalToConstant: 10).isActive = true
bottomSeparator.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
self.hiddenViewBottomConstraint?.isActive = false
self.hiddenViewTopConstraint?.isActive = true
}
#objc func toggle()
{
}
}
The layout of contentView is not updating. You should try
cell.contentView.layoutIfNeeded()
Try and share results.
I finally found that I should call super.layoutSubviews() in MyViewParentCell.swift or simply remove the function to fix the issue.
override func layoutSubviews()
{
super.layoutSubviews()
}

iOS Charts Marker View

I created a custom MarkerView from a .xib but it's always not centered in the proper set.
final class ChartMarkerView: MarkerView, ViewLoadable {
#IBOutlet weak var laValue: UILabel!
override required init(frame: CGRect) {
super.init(frame: frame)
xibSetup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
xibSetup()
}
func setUI() {
}
public override func refreshContent(entry: ChartDataEntry, highlight: Highlight) {
laValue.text = String(entry.y)
layoutIfNeeded()
}
}
protocol ViewLoadable {
var nibName: String { get }
}
extension ViewLoadable where Self: UIView {
var containerView: UIView? { return subviews.first }
var nibName: String { return String(describing: type(of: self)) }
func loadViewFromXib() -> UIView {
let bundle = Bundle(for: type(of: self))
let nib = UINib(nibName: nibName, bundle: bundle)
let view = nib.instantiate(withOwner: self, options: nil).first as! UIView
return view
}
func xibSetup() {
let view = loadViewFromXib()
view.frame = bounds
view.autoresizingMask = [.flexibleWidth, .flexibleHeight, .flexibleBottomMargin, .flexibleTopMargin]
view.autoresizesSubviews = true
backgroundColor = .clear
addSubview(view)
}
}
Turns out I just need to calculate the offset of the MarkerView and set it when loading the view:
offset.x = -self.frame.size.width / 2.0
offset.y = -self.frame.size.height - 7.0

cellForRowAt not being called but numberOfRowsInSection is

So, I have checked that tableview.reloadData() is being called on the main thread and that numberOfRowsInSection has values. When I look at the view hierarchy the tableView is clearly there and shows the background colour when set. What I have noticed that is odd is that the view hierarchy says its an Empty list(understandable) but it contains two UIImageView's one in the top right and one in the bottom left. Don't understand this at all. My tableView is nested in an iCarousel which I'm sure is where the issue lies. My code is entirely programmatic, so here is what i think may be of value:
iCarouselDatasource:(carousels dont have cells but views instead)
class BillCarouselDataSource: NSObject, iCarouselDataSource {
var bills = [Bill]()
func numberOfItems(in carousel: iCarousel) -> Int {
return bills.count
}
func carousel(_ carousel: iCarousel, viewForItemAt index: Int, reusing view: UIView?) -> UIView {
let bill = bills[index]
let billCarouselView = BillCarouselView(bill: bill)
billCarouselView.frame = carousel.frame
billCarouselView.setupView()
return billCarouselView
}
}
iCarousel view(cell)
class BillCarouselView: UIView {
private var bill: Bill!
private let nameLabel = CarouselLabel()
private let locationLabel = CarouselLabel()
private let dateLabel = CarouselLabel()
private let splitButton = CarouselButton()
private let tableView = UITableView()
required init(bill: Bill) {
super.init(frame: .zero)
self.bill = bill
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
func setupView() {
setupHierarchy()
setupViews()
setupLayout()
}
private func setupHierarchy() {
addSubview(nameLabel)
addSubview(locationLabel)
addSubview(dateLabel)
addSubview(tableView)
addSubview(splitButton)
}
private func setupViews() {
accessibilityIdentifier = AccesID.carouselView
isUserInteractionEnabled = true
layer.cornerRadius = Layout.carouselViewCornerRadius
backgroundColor = Color.carouselView
nameLabel.text = bill.name
nameLabel.textAlignment = .center
nameLabel.accessibilityIdentifier = AccesID.carouselNameLabel
locationLabel.text = bill.location
locationLabel.textAlignment = .left
locationLabel.accessibilityIdentifier = AccesID.carouselLocationLabel
dateLabel.text = bill.creationDate
dateLabel.font = Font.carouselDateText
dateLabel.textAlignment = .right
dateLabel.accessibilityIdentifier = AccesID.carouselDateLabel
let dataSource = CarouselTableViewDataSource()
let delegate = CarouselTableViewDelegate()
tableView.backgroundColor = .clear
tableView.separatorStyle = .none
tableView.dataSource = dataSource
tableView.delegate = delegate
tableView.register(CarouselTableViewCell.classForCoder(),
forCellReuseIdentifier: "carouselTableViewCell")
dataSource.items = bill.items
tableView.reloadData()
let buttonTitle = "Split £\(bill.totalPrice())"
splitButton.setTitle(buttonTitle, for: .normal)
}
private func setupLayout() {
nameLabel.pinToSuperview(edges: [.top, .left, .right])
locationLabel.pinToSuperview(edges: [.left],
constant: Layout.spacer,
priority: .required)
locationLabel.pinTop(to: nameLabel, anchor: .bottom)
locationLabel.pinRight(to: dateLabel, anchor: .left)
dateLabel.pinToSuperview(edges: [.right],
constant: -Layout.spacer,
priority: .required)
dateLabel.pinTop(to: nameLabel, anchor: .bottom)
tableView.pinTop(to: dateLabel, anchor: .bottom)
tableView.pinToSuperview(edges: [.left, .right])
tableView.pinBottom(to: splitButton, anchor: .top)
splitButton.pinToSuperview(edges: [.left, .right, .bottom])
}
}
class CarouselTableViewDataSource: NSObject, UITableViewDataSource {
var items: [Item]? {
didSet {
let duplicatedItems = items
filteredItems = duplicatedItems?.filterDuplicates { ( $0.creationID ) }
}
}
private var filteredItems: [Item]!
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return filteredItems.count
}
//Set what each cell in the tableview contains.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: CarouselTableViewCell = tableView.dequeueReusableCell(withIdentifier: "carouselTableViewCell") as! CarouselTableViewCell
let duplicateItems = items?.filter { Int($0.creationID)! == indexPath.row }
let quantity = "\(duplicateItems!.count) x "
let name = duplicateItems![0].name
let price = formatPrice(duplicateItems![0].price)
cell.quantityLabel.text = quantity
cell.nameLabel.text = name
cell.priceLabel.text = price
return cell
}
private func formatPrice(_ stringPrice: String) -> String {
var formattedPrice = stringPrice
let price = NSNumber(value: Double(stringPrice)!)
let formatter = NumberFormatter()
formatter.numberStyle = .currency
if let formattedTipAmount = formatter.string(from: price) {
formattedPrice = "Tip Amount: \(formattedTipAmount)"
}
return formattedPrice
}
}
class CarouselTableViewCell: UITableViewCell {
var containerView = UIView()
var quantityLabel = UILabel()
var nameLabel = UILabel()
var priceLabel = UILabel()
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: "carouselTableViewCell")
setupHierarchy()
setupViews()
setupLayout()
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:)")
}
private func setupHierarchy() {
containerView.addSubview(quantityLabel)
containerView.addSubview(nameLabel)
containerView.addSubview(priceLabel)
containerView.addSubview(containerView)
contentView.addSubview(containerView)
}
private func setupViews() {
quantityLabel.textAlignment = .left
quantityLabel.backgroundColor = .clear
quantityLabel.textColor = Color.carouselText
nameLabel.textAlignment = .left
nameLabel.backgroundColor = .clear
nameLabel.textColor = Color.carouselText
priceLabel.textAlignment = .right
priceLabel.backgroundColor = .clear
priceLabel.textColor = Color.carouselText
}
private func setupLayout() {
containerView.pinToSuperview(edges: [.left, .right])
containerView.pinToSuperviewTop(withConstant: -2, priority: .required, relatedBy: .equal)
containerView.pinToSuperviewBottom(withConstant: -2, priority: .required, relatedBy: .equal)
quantityLabel.pinToSuperview(edges: [.top, .left, .bottom])
nameLabel.pinToSuperview(edges: [.top, .bottom])
nameLabel.pinLeft(to: quantityLabel, anchor: .right)
nameLabel.pinRight(to: priceLabel, anchor: .left)
priceLabel.pinToSuperview(edges: [.top, .right, .bottom])
}
}
Everything in the iCarousel view(cell) shows apart from the contents of the cell.
Any help or insight would be great