Post

Replies

Boosts

Views

Activity

View a history of project states in Xcode 26
I used to be able to do that by clicking on main, as the documentation says, whereas now nothing happens. I believe this is Xcode 26 Beta 3. MacBook Air M1 8GB, macOS 15.5. I need to get to 220 characters, so I may as well say I've also tried double clicking etc. Does anyone kindly have any suggestions?
2
0
187
Jul ’25
SwiftUI previews not loading
Hello, I am a UIKit developer and I would like to try out SwiftUI. Unfortunately, my previews don't load. My situation is like the one described in this blog post: https://forums.developer.apple.com/forums/thread/704036. Unfortunately I can't update Xcode like that developer did. What I've tried: quitting and restarting Xcode, restarting my computer, resetting the simulator, deleting the derived data folder, creating new projects without storage options, test bundles or source control, editing the content view of the initial Hello World file. To be clear, I've just started learning about SwiftUI, just yesterday evening, and the previews have never loaded. Is the problem solvable? If so, how?
3
0
1.3k
Feb ’24
Programmatic UICollectionViewController
I'm making a UIKit app with no storyboard. This is my scene delegate: import UIKit class SceneDelegate: UIResponder, UIWindowSceneDelegate { var window: UIWindow? func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { guard let windowScene = (scene as? UIWindowScene) else { return } let window = UIWindow(windowScene: windowScene) window.makeKeyAndVisible() window.rootViewController = UINavigationController(rootViewController: ViewController()) self.window = window } } I've noticed that if I subclass ViewController to UICollectionViewController, the app crashes with message "Thread 1: "UICollectionView must be initialized with a non-nil layout parameter"": import UIKit class ViewController: UICollectionViewController { } It looks like I necessarily need to override the initializer: import UIKit class ViewController: UICollectionViewController { init() { super.init(collectionViewLayout: .init()) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } } I would indeed like to pass the final collection view layout in super.init(collectionViewLayout:), but defining the trailing actions before that isn't possible since self hasn't been initialized yet. So this is what I'm stuck with: import UIKit class ViewController: UICollectionViewController { init() { super.init(collectionViewLayout: .init()) var configuration = UICollectionLayoutListConfiguration(appearance: .insetGrouped) var layout = UICollectionViewCompositionalLayout.list(using: configuration) configuration.trailingSwipeActionsConfigurationProvider = { [weak self] indexPath -> UISwipeActionsConfiguration? in // access a property of self return .init(actions: [.init(style: .destructive, title: "Hello", handler: { _,_,_ in print("Handled") })]) } layout = UICollectionViewCompositionalLayout.list(using: configuration) collectionView.collectionViewLayout = layout } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } } Is this all valid?
Topic: UI Frameworks SubTopic: UIKit
3
0
661
Jul ’24
Reconfigure UICollectionView section
Problem When I reconfigure a collection view snapshot and apply it to my complex production collection view with list layout, the UI updates slowly, unless I don't animate the differences, even though I'm only reconfiguring the specific item identifiers of the cells that should be updated. I thought that maybe I could speed up the animations of the UI updates by applying the snapshot to a specific section (dataSource?.apply(sectionSnapshot, to: .main)) instead of to the whole collection view (dataSource?.apply(wholeSnapshot)). The problem is that I can't reconfigure the item identifiers of a single section snapshot to then apply the updated snapshot. Question Given the following sample app, can anybody edit reconfigureMainSection() so that, when invoked, the .main section cell registrations are called, consequently invoking print("Reconfiguring")? Sample app This collection view with list layout and diffable data source has 1 section and 2 rows. You can tap the right bar button item to call reconfigureMainSection(). class ViewController: UICollectionViewController { var snapshot: NSDiffableDataSourceSnapshot<Section, String> { var snapshot = NSDiffableDataSourceSnapshot<Section, String>() snapshot.appendSections([.main]) snapshot.appendItems(["one", "two"], toSection: .main) return snapshot } var dataSource: UICollectionViewDiffableDataSource<Section, String>? enum Section { case main } init() { super.init(collectionViewLayout: .init()) collectionView.collectionViewLayout = createLayout() configureDataSource() } override func viewDidLoad() { super.viewDidLoad() navigationItem.rightBarButtonItem = .init( title: "Reconfigure", style: .plain, target: self, action: #selector(reconfigureMainSection) ) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } func configureDataSource() { let cellRegistration = UICollectionView.CellRegistration<UICollectionViewListCell, String> { cell, indexPath, itemIdentifier in print("Reconfiguring") } dataSource = .init(collectionView: collectionView) { collectionView, indexPath, itemIdentifier in collectionView.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item: itemIdentifier) } dataSource?.apply(self.snapshot, animatingDifferences: false) } func createLayout() -> UICollectionViewLayout { return UICollectionViewCompositionalLayout { section, layoutEnvironment in let config = UICollectionLayoutListConfiguration(appearance: .plain) return NSCollectionLayoutSection.list(using: config, layoutEnvironment: layoutEnvironment) } } @objc func reconfigureMainSection() { var sectionSnapshot = NSDiffableDataSourceSectionSnapshot<String>() sectionSnapshot.append(snapshot.itemIdentifiers(inSection: .main)) // reconfigure the section snapshot dataSource?.apply(sectionSnapshot, to: .main) } } Environment MacBook Air M1 8GB macOS Sonoma 14.5 Xcode 15.4 iPhone 15 Pro simulator on iOS 17.5
Topic: UI Frameworks SubTopic: UIKit
3
0
488
Aug ’24
UITextView scrolling indicator cut off at top
Sample app A collection view controller with list layout, 1 section and 1 row. The cell's content view contains a text view. class ViewController: UICollectionViewController { var snapshot: NSDiffableDataSourceSnapshot<Section, String> { var snapshot = NSDiffableDataSourceSnapshot<Section, String>() snapshot.appendSections([.main]) snapshot.appendItems(["one", "two"], toSection: .main) return snapshot } var dataSource: UICollectionViewDiffableDataSource<Section, String>? enum Section { case main } init() { super.init(collectionViewLayout: .init()) collectionView.collectionViewLayout = createLayout() configureDataSource() // more likely and automatically avoid unpleasant animations on iOS 15 by configuring the data source in the init rather than in view did load } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } func configureDataSource() { let cellRegistration = UICollectionView.CellRegistration<UICollectionViewListCell, String> { cell, indexPath, itemIdentifier in let textView = UITextView() textView.font = .systemFont(ofSize: UIFont.labelFontSize) cell.contentView.addSubview(textView) textView.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ textView.topAnchor.constraint(equalTo: cell.contentView.topAnchor, constant: 8), textView.bottomAnchor.constraint(equalTo: cell.contentView.bottomAnchor, constant: -8), textView.leadingAnchor.constraint(equalTo: cell.contentView.leadingAnchor, constant: cell.directionalLayoutMargins.leading), textView.trailingAnchor.constraint(equalTo: cell.contentView.trailingAnchor, constant: -cell.directionalLayoutMargins.trailing) ]) } dataSource = .init(collectionView: collectionView) { collectionView, indexPath, itemIdentifier in collectionView.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item: itemIdentifier) } dataSource?.apply(self.snapshot, animatingDifferences: false) } func createLayout() -> UICollectionViewLayout { return UICollectionViewCompositionalLayout { section, layoutEnvironment in let config = UICollectionLayoutListConfiguration(appearance: .insetGrouped) return NSCollectionLayoutSection.list(using: config, layoutEnvironment: layoutEnvironment) } } } Question 1 Can anybody edit the provided sample code so that the text view's vertical indicator inset is not cut off at the top? Question 2 It seems to me that Apple has successfully implemented text views inside table view cells: can anybody provide Apple documentation as per how to do so? What I've tried and didn't work textView.verticalScrollIndicatorInsets.top = 30 // does nothing Adding the text view to a custom view and the view to the cell's content view textView.contentInset = .zero textView.scrollIndicatorInsets = .zero textView.textContainerInset = .zero textView.textContainer.lineFragmentPadding = 0 Centering the text view vertically and constraining its height to that of the content view with an 8 points constant to leave some padding Constraining the top and bottom anchors of the text view to the cell's layout margins guide's top and bottom anchors Constraint I need the text view to have some padding from the top and bottom of the cell for aesthetic reasons.
Topic: UI Frameworks SubTopic: UIKit Tags:
3
0
578
Aug ’24
Handle keyboard layout in iOS 15+ UIKit app with collection view using the modern approach
The following is a UIKit app that uses a collection view with list layout and a diffable data source. It displays one section that has 10 empty cells and then a final cell whose content view contains a text view, that is pinned to the content view's layout margins guide. The text view's scrolling is set to false, so that the line collectionView.selfSizingInvalidation = .enabledIncludingConstraints will succeed at making the text view's cell resize automatically and animatedly as the text changes. import UIKit class ViewController: UIViewController { var collectionView: UICollectionView! var dataSource: UICollectionViewDiffableDataSource<String, Int>! let textView: UITextView = { let tv = UITextView() tv.text = "Text" tv.isScrollEnabled = false return tv }() override func viewDidLoad() { super.viewDidLoad() configureHierarchy() configureDataSource() if #available(iOS 16.0, *) { collectionView.selfSizingInvalidation = .enabledIncludingConstraints } } func configureHierarchy() { collectionView = .init(frame: .zero, collectionViewLayout: createLayout()) view.addSubview(collectionView) collectionView.frame = view.bounds collectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight] } func createLayout() -> UICollectionViewLayout { let configuration = UICollectionLayoutListConfiguration(appearance: .insetGrouped) return UICollectionViewCompositionalLayout.list(using: configuration) } func configureDataSource() { let cellRegistration = UICollectionView.CellRegistration<UICollectionViewListCell, Int> { _, _, _ in } let textViewCellRegistration = UICollectionView.CellRegistration<UICollectionViewListCell, Int> { [weak self] cell, _, _ in guard let self else { return } cell.contentView.addSubview(textView) textView.pin(to: cell.contentView.layoutMarginsGuide) } dataSource = .init(collectionView: collectionView) { collectionView, indexPath, itemIdentifier in if indexPath.row == 10 { collectionView.dequeueConfiguredReusableCell(using: textViewCellRegistration, for: indexPath, item: itemIdentifier) } else { collectionView.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item: itemIdentifier) } } var snapshot = NSDiffableDataSourceSnapshot<String, Int>() snapshot.appendSections(["section"]) snapshot.appendItems(Array(0...10)) dataSource.apply(snapshot) } } extension UIView { func pin( to object: CanBePinnedTo, top: CGFloat = 0, bottom: CGFloat = 0, leading: CGFloat = 0, trailing: CGFloat = 0 ) { self.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ self.topAnchor.constraint(equalTo: object.topAnchor, constant: top), self.bottomAnchor.constraint(equalTo: object.bottomAnchor, constant: bottom), self.leadingAnchor.constraint(equalTo: object.leadingAnchor, constant: leading), self.trailingAnchor.constraint(equalTo: object.trailingAnchor, constant: trailing), ]) } } @MainActor protocol CanBePinnedTo { var topAnchor: NSLayoutYAxisAnchor { get } var bottomAnchor: NSLayoutYAxisAnchor { get } var leadingAnchor: NSLayoutXAxisAnchor { get } var trailingAnchor: NSLayoutXAxisAnchor { get } } extension UIView: CanBePinnedTo { } extension UILayoutGuide: CanBePinnedTo { } How do I make the UI move to accomodate the keyboard once you tap on the text view and also when the text view changes size, by activating the view.keyboardLayoutGuide.topAnchor constraint, as shown in the WWDC21 video "Your guide to keyboard layout"? My code does not resize the text view on iOS 15, only on iOS 16+, so clearly the solution may as well allow the UI to adjust to changes to the text view frame on iOS 16+ only. Recommended, modern, approach: Not recommended, old, approach: Here's what I've tried and didn’t work on the Xcode 15.3 iPhone 15 Pro simulator with iOS 17.4 and on my iPhone SE with iOS 15.8: view.keyboardLayoutGuide.topAnchor.constraint(equalTo: textView.bottomAnchor).isActive = true in the text view cell registration view.keyboardLayoutGuide.topAnchor.constraint(equalTo: collectionView.bottomAnchor).isActive = true in viewDidLoad() pinning the bottom of the collection view to the top of the keyboard: func configureHierarchy() { collectionView = UICollectionView(frame: .zero, collectionViewLayout: createLayout()) view.addSubview(collectionView) collectionView.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ collectionView.topAnchor.constraint(equalTo: view.topAnchor), collectionView.bottomAnchor.constraint(equalTo: view.keyboardLayoutGuide.topAnchor), collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor), collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor) ]) } To be more specific, I only tried this last approach on the simulator, and it moved the UI seemingly twice as much as it should have, and the tab bar of my tab bar controller was black, which discouraged me although there might be a proper (non-workaround) solution for iOS 17 only (i.e. saying view.keyboardLayoutGuide.usesBottomSafeArea = false). The first 2 approaches just didn't work instead. Setting the constraints priority to .defaultHigh doesn't do it.
Topic: UI Frameworks SubTopic: UIKit
4
0
1.3k
Jul ’24
Setting the `backgroundColor` property of UIBackgroundConfiguration breaks the default UIConfigurationColorTransformer
The following is a UIKit app that displays a collection view with list layout and diffable data source (one section, one row). class ViewController: UIViewController { var collectionView: UICollectionView! var dataSource: UICollectionViewDiffableDataSource<String, String>! override func viewDidLoad() { super.viewDidLoad() configureHierarchy() configureDataSource() } func configureHierarchy() { collectionView = .init(frame: .zero, collectionViewLayout: createLayout()) view.addSubview(collectionView) collectionView.frame = view.bounds collectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight] } func createLayout() -> UICollectionViewLayout { UICollectionViewCompositionalLayout { section, layoutEnvironment in let config = UICollectionLayoutListConfiguration(appearance: .insetGrouped) return NSCollectionLayoutSection.list(using: config, layoutEnvironment: layoutEnvironment) } } func configureDataSource() { let cellRegistration = UICollectionView.CellRegistration<UICollectionViewListCell, String> { cell, indexPath, itemIdentifier in var backgroundConfiguration = UIBackgroundConfiguration.listGroupedCell() backgroundConfiguration.backgroundColor = .systemBlue cell.backgroundConfiguration = backgroundConfiguration } dataSource = .init(collectionView: collectionView) { collectionView, indexPath, itemIdentifier in collectionView.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item: itemIdentifier) } var snapshot = NSDiffableDataSourceSnapshot<String, String>() snapshot.appendSections(["main"]) snapshot.appendItems(["demo"]) dataSource.apply(snapshot, animatingDifferences: false) } } If you tap on the row, it seems like selection doesn't happen: giving the cell a blue background broke its default background color transformer. Here's what I've tried and didn't work: Setting the collection view's delegate and specifying that you can select any row Setting the color transformer to .grayscale Setting the backgroundConfiguration to UIBackgroundConfiguration.listGroupedCell().updated(for: cell.configurationState) Combinations of the approaches above Setting the color transformer to UIBackgroundConfiguration.listGroupedCell().backgroundColorTransformer and cell.backgroundConfiguration?.backgroundColorTransformer (they're both nil) Setting the cell's backgroundColor directly I also considered using a custom color transformer: var backgroundConfiguration = UIBackgroundConfiguration.listGroupedCell() backgroundConfiguration.backgroundColorTransformer = UIConfigurationColorTransformer { _ in if cell.configurationState.isSelected || cell.configurationState.isHighlighted { .systemBlue.withAlphaComponent(0.7) } else { .systemBlue } } cell.backgroundConfiguration = backgroundConfiguration However, if you push a view controller when you select the row, the row gets deselected, which is unfortunate, giving that I like to deselect rows in viewWillAppear(_:) to keep users more oriented. There might be ways to circumvent this, but my custom color transformer might still differ from the default one in some other ways. So how do I assign the default one to my cell?
Topic: UI Frameworks SubTopic: UIKit
4
0
590
Aug ’24