import UIKit
final class CommonSectionHeader: UIView {
@IBOutlet var titleLabel: UILabel!
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
func commonInit() {
let bundle = Bundle(for: type(of: self))
let nib = UINib(nibName: "\(CommonSectionHeader.self)", bundle: bundle)
guard let view = nib.instantiate(withOwner: self, options: nil).first as? UIView else {
fatalError()
}
addSubview(view)
view.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
view.topAnchor.constraint(equalTo: topAnchor),
view.leadingAnchor.constraint(equalTo: leadingAnchor),
view.trailingAnchor.constraint(equalTo: trailingAnchor),
view.bottomAnchor.constraint(equalTo: bottomAnchor),
])
}
}
-
-
Save tx-TEM/39e7dd74473b95543d8633d3f0f919db to your computer and use it in GitHub Desktop.
guard let text = textLabel.text, let font = textLabel.font else { return }
let labelHeight = text.boundingRect(with: CGSize(width: UIScreen.main.bounds.width,
height: CGFloat.greatestFiniteMagnitude),
options: [.usesLineFragmentOrigin, .usesFontLeading],
attributes: [.font: font],
context: nil).height
// array.indices(インデックスの配列)とcontains(配列に含まれるか)を利用する
guard array.indices.contains(index) else { return }
enum Fruits: CaseIterable {
case apple, orange, banana
}
Fruits.allCases.count // => 3
Fruits.allCases // => [Fruits.apple, Fruits.orange, Fruits.banana]
http://blog.penginmura.tech/entry/2018/04/14/202031
enum Fruits: String, CaseIterable {
case apple = "りんご"
case orange = "みかん"
case banana = "バナナ"
}
let list = Fruits.allCases.map { $0.rawValue } // => ["りんご"、"みかん"、"バナナ"]
// stroryBoardから設定すると上手くいかないのでセルに対して直接制約はる
import UI
import UIKit
class OtherSettingCell: UITableViewCell {
override func awakeFromNib() {
super.awakeFromNib()
commonInit()
}
func commonInit() {
let lineView = UIView()
lineView.backgroundColor = UIColor.black
lineView.translatesAutoresizingMaskIntoConstraints = false
addSubview(lineView)
NSLayoutConstraint.activate([
lineView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 16),
lineView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -16),
lineView.bottomAnchor.constraint(equalTo: bottomAnchor),
lineView.heightAnchor.constraint(equalToConstant: 1)
])
}
}
cell.selectionStyle = .none
func tableView(_ tableView: UITableView, didHighlightRowAt indexPath: IndexPath) {
let cell = tableView.cellForRow(at: indexPath) as? BannerCell
cell?.bannerImageView.backgroundColor = .darkGray
}
func tableView(_ tableView: UITableView, didUnhighlightRowAt indexPath: IndexPath) {
let cell = tableView.cellForRow(at: indexPath) as? BannerCell
cell?.bannerImageView.backgroundColor = .clear
}
switch (detail.payment, detail.income) {
case let (payment?, income?):
print(payment)
print(income)
case let (payment?, _):
print(payment)
case let (_ , income?):
print(income)
default:
assertionFailure()
}
参考: https://teratail.com/questions/158192#reply-237348
条件に?が付いているとnil検査が先に行われ、nilでない場合に条件が検査されます。
viewModel.foo.producer.take(during: self.reactive.lifetime).startWithCompleted {
print("completed")
}
監視はcompletedされない限り続くはず(多分)なので、deinitで明示的にdisposeするか、不要になったタイミングでcompletedさせる必要があるはず。
https://stackoverflow.com/questions/48025317/what-difference-between-using-scopeddisposable-and-takeduring https://eliaszsawicki.com/reactiveswift-manage-your-memory/
let label = UILabel()
let attrText = NSMutableAttributedString()
let colorAttributes = [NSAttributedString.Key.foregroundColor: UserColorManager().currentTheme.stdColor]
attrText.append(NSMutableAttributedString(string: "色なし "))
attrText.append(NSMutableAttributedString(string: "色ある", attributes: colorAttributes))
label.attributedText = attrText
extension Reactive where Base: UIViewController {
public var viewDidDisappear: Signal<(), Never> {
return trigger(for: #selector(UIViewController.viewDidDisappear(_:)))
}
public var viewWillDisappear: Signal<(), Never> {
return trigger(for: #selector(UIViewController.viewWillDisappear(_:)))
}
public var viewDidAppear: Signal<(), Never> {
return trigger(for: #selector(UIViewController.viewDidAppear(_:)))
}
public var viewWillAppear: Signal<(), Never> {
return trigger(for: #selector(UIViewController.viewWillAppear(_:)))
}
}
signal
.take(during: reactive.lifetime)
.sample(on: reactive.viewDidAppear)
.observeValues { [weak self] _ in
//reloadとか
}
RxSwift: https://tech-blog.sgr-ksmt.org/2016/04/23/viewcontroller_trigger/
let attrText = NSMutableAttributedString()
attrText.append(NSMutableAttributedString(string: "xxx", attributes: attributes1))
attrText.append(NSMutableAttributedString(string: "yyy", attributes: attributes2))
// fontサイズだけ後から指定したい場合など
// (AttributedTextはautoShrinkが効かないことがあるので、自前で修正するときに使える)
let range = NSRange(location: 0, length: attrText.length)
attrText.addAttribute(NSAttributedString.Key.font, value: UIFont.systemFont(ofSize: 10), range: range)
func ajustedFont(text: String, width: CGFloat, fontSize: CGFloat, weight: UIFont.Weight) -> UIFont {
let str = NSString(string: text)
var fSize = fontSize
while str.size(withAttributes: [
.font: UIFont.systemFont(ofSize: fSize, weight: weight),
]).width >= width {
fSize -= 0.5
}
return UIFont.systemFont(ofSize: fSize, weight: weight)
}
extension ExampleViewModel {
typealias Section = ExampleSection
typealias RowType = ExampleRow
enum NotificationSettingSection: CaseIterable {
case secA, secB
var rows: [RowType] {
switch self {
case .secA:
return [.rowA]
case .secB:
return [.rowB, .rowC]
}
}
}
enum NotificationSettingRow {
case rowA, rowB, rowC
}
var sections: [Section] {
return Section.allCases
}
func sectionFor(section: Int) -> Section? {
guard sections.indices.contains(section) else { return nil }
return sections[section]
}
func rowFor(indexPath: IndexPath) -> RowType? {
guard let section = sectionFor(section: indexPath.section), section.rows.indices.contains(indexPath.row) else {
return nil
}
return section.rows[indexPath.row]
}
}
参考: https://scior.hatenablog.com/entry/2020/03/02/230404
let urlString: String? = "url"
// flatMapを使うことでurlStringのunwrapを無くせる
let url: URL? = urlString.flatMap(URL.init(string:))
struct Data {
var str: String
var num: Int
init(num: Int) {
str = String(num)
}
}
[1, 2, 3, 4, 5].map(Data.init(num:))
参考: https://stackoverflow.com/questions/6216839/how-to-add-spacing-between-uitableviewcell
contentView配下に任意のサイズのView(以後 InnerView)を追加し、タップ範囲をInnerViewに限定する
class ExampleCell: UITableViewCell {
@IBOutlet var innerView: UIView!
// innerView配下のviewのみをタッチ対象とする
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
let view = super.hitTest(point, with: event)
// 余白部分が対象になった場合は無視
if view == contentView {
return nil
}
return view
}
override func setSelected(_ selected: Bool, animated: Bool) {
highlightBackgroundColor(selected, animated: animated)
}
override func setHighlighted(_ highlighted: Bool, animated: Bool) {
highlightBackgroundColor(highlighted, animated: animated)
}
func highlightBackgroundColor(_ highlighted: Bool, animated: Bool) {
if animated {
UIView.animate(withDuration: 0.5) {
self.highlightBackgroundColor(highlighted, animated: false)
}
} else {
innerView.backgroundColor = highlighted ? .lightGray : .white
}
}
}
struct BarChart: View {
var rate: Double
var body: some View {
GeometryReader { geometry in
HStack(alignment: .center, spacing: 0) {
Color(UIColor.red)
.frame(width: geometry.size.width * CGFloat(rate),
height: geometry.size.height, alignment: .center)
Color(UIColor.gray)
.frame(width: 1 - (geometry.size.width * CGFloat(rate)),
height: geometry.size.height, alignment: .center)
}
.mask(RoundedRectangle(cornerRadius: 4))
}
}
}
全ての要素のサイズを指定しないと、上手く描画されない模様。
https://qiita.com/fromkk/items/de4cbebb3c39ac3888cc
https://twitter.com/k_katsumi/status/1255456988944912387?s=20
headerView.translatesAutoresizingMaskIntoConstraints = false
tableView.tableHeaderView = headerView
NSLayoutConstraint.activate([
headerView.topAnchor.constraint(equalTo: tableView.topAnchor),
headerView.widthAnchor.constraint(equalTo: tableView.widthAnchor),
headerView.centerXAnchor.constraint(equalTo: tableView.centerXAnchor)])
headerView.layoutIfNeeded()
tableView.tableHeaderView = headerView
メモ: tabelHeaderViewの高さが低くなったときに、headerとセルの間に余白ができてしまうことがある。そういう場合はtableView.reloadDataすると直る
override public func preferredContentSizeDidChange(forChildContentContainer _: UIContentContainer) {
guard let containerView = containerView else {
return
}
UIView.animate(withDuration: 0.3) {
containerView.setNeedsLayout()
containerView.layoutIfNeeded()
}
}
override public func containerViewWillLayoutSubviews() {
super.containerViewWillLayoutSubviews()
presentedView!.frame = frameOfPresentedViewInContainerView
}
合法にUITabBarButtonを取得する https://qiita.com/rnitta/items/b8e82472b3876d345e6a
NSLayoutConstraint.activate([
imageView.leadingAnchor.constraint(greaterThanOrEqualTo: containerView.leadingAnchor),
imageView.trailingAnchor.constraint(lessThanOrEqualTo: containerView.trailingAnchor),
imageView.centerXAnchor.constraint(equalTo: containerView.centerXAnchor),
imageView.widthAnchor.constraint(lessThanOrEqualToConstant: 700),
])
viewがはみ出ないか心配だったので左右の余白をつけたが、もしかしたらいらないかもしれない。
https://zenn.dev/kalupas226/articles/da4b25f96c8c2d
@IBOutlet private weak var collectionView: UICollectionView!
//...
override func viewDidload() {
super.viewDidLoad()
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
layout.estimatedItemSize = CGSize(width: 1, height: 1)
layout.invalidateLayout()
collectionView.collectionViewLayout = layout
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
collectionViewHeightConstraint.constant = collectionView.collectionViewLayout.collectionViewContentSize.height
layoutIfNeeded()
}
func tableView(_: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
guard let currentSection = viewModel.sectionFor(section: indexPath.section) else {
return 0
}
switch currentSection {
case .item:
return 100
case .footer:
return UITableView.automaticDimension
}
}
final class CompositionalLayoutViewController: UIViewController {
typealias ViewModel = CompositionalLayoutViewModel
typealias Section = ViewModel.Section
typealias Item = ViewModel.Item
@IBOutlet var collectionView: UICollectionView!
let viewModel = CompositionalLayoutViewModel()
private var dataSource: UICollectionViewDiffableDataSource<Section, Item>!
private var snapshot: NSDiffableDataSourceSnapshot<Section, Item>!
override func viewDidLoad() {
super.viewDidLoad()
setupCollectionView()
}
}
extension CompositionalLayoutViewController {
private func compositionalLayout() -> UICollectionViewCompositionalLayout {
let layout = UICollectionViewCompositionalLayout { [weak self] _, _ in
guard let self = self else { fatalError() }
return self.createSection()
}
return layout
}
private func createSection() -> NSCollectionLayoutSection {
let itemWidth: CGFloat = 85
let itemCount = 12
let itemSize = NSCollectionLayoutSize(widthDimension: .absolute(itemWidth), heightDimension: .absolute(44))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
let groupSize = NSCollectionLayoutSize(widthDimension: .absolute(CGFloat(itemCount) * itemWidth), heightDimension: .absolute(44))
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitem: item, count: 12)
let section = NSCollectionLayoutSection(group: group)
return section
}
private func setupCollectionView() {
collectionView.register(SampleItemCell.self, forCellWithReuseIdentifier: "\(SampleItemCell.self)")
collectionView.bounces = false
collectionView.collectionViewLayout = compositionalLayout()
dataSource = UICollectionViewDiffableDataSource<Section, Item>(collectionView: collectionView) { collectionView, indexPath, item in
switch item {
case let .sampleItem(data):
let cell = collectionView.dequeueCell("\(SampleItemCell.self)", for: indexPath) as SampleItemCell
cell.setup(parentController: self, title: "\(data.value)月")
return cell
}
}
snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
snapshot.appendSections(Section.allCases)
for i in 1 ... 12 {
snapshot.appendItems([.columnsItem(ViewModel.SampleData(value: i))], toSection: .sample)
}
dataSource.apply(snapshot, animatingDifferences: false)
}
}
struct CompositionalLayoutViewModel {}
extension CompositionalLayoutViewModel {
enum Section: CaseIterable {
case sample
}
enum Item: Hashable {
case sampleItem(SampleData)
}
struct SampleData: Hashable {
private let identifier = UUID()
var value: Int
}
}
vc.willMove(toParent: self)
addChild(vc)
vc.didMove(toParent: self)
vc.view.translatesAutoresizingMaskIntoConstraints = false
let contentView = UIView()
contentView.addSubview(vc.view)
NSLayoutConstraint.activate([
vc.view.topAnchor.constraint(equalTo: contentView.topAnchor),
vc.view.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
vc.view.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
vc.view.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
])
func createHeaderRegistration() -> UICollectionView.SupplementaryRegistration<HeaderView> {
return UICollectionView.SupplementaryRegistration<HeaderView>(
supplementaryNib: UINib(nibName: "\(OnlineAccountTutorialListHeaderView.self)", bundle: nil),
elementKind: UICollectionView.elementKindSectionHeader) { (supplementaryView, string, indexPath) in
supplementaryView.setup(title: title)
}
}
let headerRegistration = createHeaderRegistration()
dataSource.supplementaryViewProvider = { (view, kind, index) in
return self.collectionView.dequeueConfiguredReusableSupplementary(using: headerRegistration, for: index)
}
systemLayoutSizeFittingSizeを使うと簡単
let layout = UICollectionViewCompositionalLayout { sectionIndex, layoutEnvironment in
let section = NSCollectionLayoutSection.list(using: configuration, layoutEnvironment: layoutEnvironment)
if sectionIndex == 0 {
section.contentInsets = NSDirectionalEdgeInsets(top: 24, leading: 0, bottom: 0, trailing: 0)
}
return section
}
final class HostingContentViewConfiguration<T: View>: UIContentConfiguration {
private(set) weak var parentVC: UIViewController?
private(set) var content: () -> T
init(parentVC: UIViewController?, @ViewBuilder content: @escaping () -> T) {
self.parentVC = parentVC
self.content = content
}
func makeContentView() -> UIView & UIContentView {
return HostingContentView(configuration: self)
}
func updated(for _: UIConfigurationState) -> Self {
return self
}
}
final class HostingContentView<T: View>: UIView, UIContentView {
var hostingController: UIHostingController<T>
var configuration: UIContentConfiguration {
didSet {
guard let config = configuration as? HostingContentViewConfiguration else {
return
}
removeHostingControllerFromParent()
hostingController = UIHostingController(rootView: config.content())
setup(parentVC: config.parentVC)
}
}
init(configuration: HostingContentViewConfiguration) {
self.configuration = configuration
hostingController = UIHostingController(rootView: configuration.content())
super.init(frame: .zero)
setup(parentVC: configuration.parentVC)
}
@available(*, unavailable)
required init?(coder _: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
deinit {
removeHostingControllerFromParent()
}
}
extension HostingContentView {
public func setup(parentVC: UIViewController?) {
guard let parentVC = parentVC else {
return
}
let vc = hostingController
vc.view.backgroundColor = .clear
vc.willMove(toParent: parentVC)
parentVC.addChild(vc)
vc.didMove(toParent: parentVC)
vc.view.translatesAutoresizingMaskIntoConstraints = false
addSubview(vc.view)
NSLayoutConstraint.activate([
vc.view.topAnchor.constraint(equalTo: topAnchor),
vc.view.bottomAnchor.constraint(equalTo: bottomAnchor),
vc.view.leadingAnchor.constraint(equalTo: leadingAnchor),
vc.view.trailingAnchor.constraint(equalTo: trailingAnchor),
])
}
public func removeHostingControllerFromParent() {
hostingController.willMove(toParent: nil)
hostingController.view.removeFromSuperview()
hostingController.removeFromParent()
}
}
private func cellRegistration() -> UICollectionView.CellRegistration<UICollectionViewCell, Item> {
return UICollectionView.CellRegistration<UICollectionViewCell, Item> { [weak self] cell, _, _ in
cell.contentConfiguration = HostingContentViewConfiguration(parentVC: self) {
ContentView()
}
}
}
作成したViewへのBindingにはObservableObjectを直接渡す。vcでobservableObjectを持ち、ViewにPublished指定したプロパティを渡しても更新は入らない
自分の環境によるかもしれませんが、Resultsを直接扱うと、オブジェクトを削除したときに、「"Object has been deleted or invalidated."」というクラッシュが発生しました。 対策として、results.freeze()のような形にして、オブジェクトの中身が自動で更新されないようにすることで解消しました。
削除処理の例
let realm = try! Realm()
try! realm.write {
let objects = XXObjectsManager.shared.all()
realm.delete(objects)
}
対応
var objects = XXObjectsManager.shared.all().freeze()
何らかの方法でrealmのobject変更を監視 {
objects = XXObjectsManager.shared.all().freeze()
// viewの更新処理
}