Blog by Frank

UIKit Intro in Objective-C & Swift

Everything about UIKit in Objective-C & Swift when I was coding.

hws - UIKit Example Code

QuickStart

Debug Tools

Flex

#if os(iOS)
import FLEX
#endif

#if DEBUG
//import FLEX
#endif

#if DEBUG
ToolbarItemGroup {
    Button("Flex") {
        FLEXManager.shared.showExplorer()
    }
//                darkModeButton
}
#endif

Components QuickStart

graph LR

Button --> UIButton --> f2[Trigger action]

Text --> UILabel --> f1[Display text]
Text --> UITextField --> f3[Enter Signle Line Text]
Text --> UITextView --> f4[Enter Multiple Line Text]

Image --> UIImageView --> f5[Display Image]

Scroll --> UIScrollView --> UITextView

Gesture --> UIGestureRecognizer --> UITapGesture

Effect --> UIBlurEffect

UITableView
UICollectioView
UIStackView

Delegate,类扩展

@interface ViewController ()<UITextFieldDelegate, UITextViewDelegate>

@end

() 表示这是一个匿名分类(也叫类扩展或匿名类别),可以在其中添加实例变量和方法。<UICollectionViewDelegate, UICollectionViewDataSource,UICollectionViewDelegateFlowLayout> 表示这个类实现了 UICollectionViewDelegate、UICollectionViewDataSource 和 UICollectionViewDelegateFlowLayout 这三个协议。在 Objective-C 中,需要在类的声明中明确列出所实现的协议,这有助于编译器检查类是否实现了协议中规定的所有方法。

@interface TestingUICollectionViewCell ()<UICollectionViewDelegate, UICollectionViewDataSource,UICollectionViewDelegateFlowLayout>
@end
class TestingUICollectionViewCell: UICollectionViewCell, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
    // class implementation goes here
}

Button

// in file ViewController.m
- (void)viewDidLoad {
    [super viewDidLoad];

    UIButton *button = [UIButton buttonWithType:
									UIButtonTypeSystem];
    [button setTitle:@"Press Me" forState:UIControlStateNormal];
    [button sizeToFit];

	// Set a new (x,y) point for the button's center
    button.center = CGPointMake(320 / 2, 60);

	// Add an action in current code file (i.e. target)
    [button addTarget:self 
			action:@selector(buttonPressed:
			forControlEvents:UIControlEventTouchUpInside];

    [self.view addSubview:button];
} 

- (void)buttonPressed:(UIButton *)button {
     NSLog(@"Button Pressed");
}

// Another Button
- (void)viewDidLoad {
    [super viewDidLoad];

    UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake(100, 100, 200, 200)];
    [button setTitle:@"hello" forState:UIControlStateNormal];
    [button addTarget:self action:@selector(buttonPressed) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:button];
}

- (void)buttonPressed {
    NSLog(@"Hello world");
}

UILabel & UITextField & UITextView

//
//  ViewController.m
//  DouyinObjc
//
//  Created by Chu Yong on 5/13/23.
//

#import "ViewController.h"

@interface ViewController ()<UITextFieldDelegate, UITextViewDelegate>

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor redColor];
    UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(100, 100, 100, 100)];
    label.text = @"Hello World";
    label.backgroundColor = [UIColor whiteColor];
    [self.view addSubview:label];
    
    // TextFields
    UITextField *textfield = [[UITextField alloc] initWithFrame:CGRectMake(0, 200, UIScreen.mainScreen.bounds.size.width, 200)];
    textfield.text = @"Hello";
    textfield.placeholder = @"Enter something";
    textfield.delegate = self;
    [self.view addSubview:textfield];
    
    // TextView
    UITextView *textView = [[UITextView alloc] initWithFrame:CGRectMake(0, 400, UIScreen.mainScreen.bounds.size.width, 200)];
    textView.text = @"Hello";
    textView.backgroundColor = [UIColor yellowColor];
    textView.delegate = self;
    [self.view addSubview:textView];
}

- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {
    if ([text isEqualToString:@"\n"]) {
        [textView resignFirstResponder];
    }
    return YES;
}

- (BOOL)textFieldShouldReturn:(UITextField *)textField {
    [textField resignFirstResponder];
    return YES;
}

- (void)textFieldDidBeginEditing:(UITextField *)textField {
    NSLog(@"%@", textField.text);
}
@end

Masonry Constraint for UILabel

layoutMargins

Remove UILabel padding

//        _contentLabel.directionalLayoutMargins = NSDirectionalEdgeInsetsMake(0, 0, 0, 0);
//        _contentLabel.layoutMargins = UIEdgeInsetsMake(0, 0, 0, 0);

directionalLayoutMargins

directionalLayoutMargins 是UIView的一个属性,用于指定在考虑当前语言方向时布局视图内容时使用的默认间距。这个属性的类型是NSDirectionalEdgeInsets,该结构具有leading,top,trailing和bottom四个方向的边距。

leading和trailing的概念是相对的,并且依赖于用户界面的当前语言环境。在从左到右的语言环境中,leading边距位于左边,trailing边距位于右边;而在从右到左的语言环境中,这两者会互换位置。

使用directionalLayoutMargins可以帮助您在支持右到左语言环境的应用中实现适应性布局。以下是一个使用directionalLayoutMargins的例子:

objc Copy code // 创建一个新的视图 UIView *newView = [[UIView alloc] init];

// 设置视图的方向性边距 newView.directionalLayoutMargins = NSDirectionalEdgeInsetsMake(10, 20, 30, 40);

// 通过代码或者自动布局约束来进行布局 // 当在一个从右到左的语言环境中查看这个视图时,leading边距会位于右边,trailing边距会位于左边 当然,需要注意的是,directionalLayoutMargins并不会直接改变视图的内容布局。要使directionalLayoutMargins生效,还需要结合使用AutoLayout约束,特别是在创建约束时使用NSLayoutAnchor的leadingAnchor和trailingAnchor,而不是leftAnchor和rightAnchor。

UIImage

// UIImage
UIImage *image = [UIImage systemImageNamed:@"plus"];
UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
imageView.frame = CGRectMake(50, 50, 20, 200);
imageView.contentMode = UIViewContentModeScaleAspectFill;
imageView.layer.borderWidth = 1.0;
imageView.layer.borderColor = [UIColor yellowColor].CGColor;
[self.view addSubview:imageView];

Aspect Fit

UITableView & UICollectionView

【抖音OC实战教程-View讲解03(05)】

UITableView 和 UICollectionView 是 iOS 中常用的两种列表控件。

UITableView 是一个列表控件,可以用来展示单行或多行数据。它是基于 Model-View-Controller 设计模式的。UITableView 中的每个 cell 都可以由一个 UITableViewCell 类型的实例表示,而这个实例通常包含了一些 UI 元素和一些用来展示数据的控件。UITableView 中可以设置 section 来分组显示数据。

UICollectionView 是一个强大的集合控件,可以用来展示多行、多列的数据。它也是基于 Model-View-Controller 设计模式的。与 UITableView 不同,UICollectionView 的布局方式更加灵活,可以通过设置 layout 来控制每个 cell 的大小、位置、对齐方式等等。与 UITableView 相似,UICollectionView 也可以使用 section 来分组显示数据,而且它还支持一些特殊的布局方式,如流式布局等。

总的来说,UITableView 适用于简单的列表展示,而UICollectionView 则更加适用于展示多样化、样式复杂的数据,同时它的布局方式更加灵活。

//
//  ViewController.m
//  DouyinObjc
//
//  Created by Chu Yong on 5/13/23.
//

#import "ViewController.h"

@interface ViewController ()<UICollectionViewDelegate, UICollectionViewDataSource>
@property (nonatomic, strong) NSMutableArray <NSArray *>*sections;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // UICollectionView
    self.sections = [NSMutableArray array];
    [self.sections addObject:@[@"123"]];
    [self.sections addObject:@[@"3", @"4"]];
    [self.sections addObject:@[@"3", @"4", @"3", @"4"]];

    UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
    UICollectionView *collectionView = [[UICollectionView alloc] initWithFrame:self.view.bounds collectionViewLayout:layout];
    
    layout.itemSize = CGSizeMake(100, 200);
    layout.minimumLineSpacing = 20;
    layout.sectionInset = UIEdgeInsetsMake(0, 0, 50, 0);
    [collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:@"cell"];
    
    collectionView.delegate = self;
    collectionView.dataSource = self;
    
    [self.view addSubview:collectionView];
}

- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
    return self.sections.count;
}

- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
    return self.sections[section].count;
}

- (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
    UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"cell" forIndexPath:indexPath];
    
    UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(10, 10, 50, 50)];
    label.text = self.sections[indexPath.section][indexPath.row];
    [cell addSubview:label];
    
    cell.backgroundColor = [UIColor redColor];
    return cell;
}
@end

Header footer for UITableView

iOS-UITableViewCell自适应高度最优雅的方法

隐藏分割线

iOS 中隐藏UITableView最后一条分隔线

// 分割线
// _tableView.separatorStyle = UITableViewCellSeparatorStyleSingleLine;
if (indexPath.row == _menuStringArray.count-1) {
    menuCell.separatorInset = UIEdgeInsetsMake(0, self.bounds.size.width, 0, 0);
} else{
    menuCell.separatorInset = UIEdgeInsetsMake(0, FTDefaultMenuTextMargin, 0, 10+FTDefaultMenuTextMargin);
}

Grouped UITableview remove outer separator line

Grouped UITableview remove outer separator line

// Get the width of tableview
// https://stackoverflow.com/questions/29006311/grouped-uitableview-remove-outer-separator-line
for (UIView *subview in cell.subviews) {
    if (subview != cell.contentView && subview.frame.size.width == cell.width) {
        [subview removeFromSuperview];
    }
}

Protocol & UICollectionView

// ViewController.h
// <> Means Controller conform delegate inside <>
@interface ViewController : UIViewController <UICollectionViewDelegate, UICollectionViewDataSource>
@end
@interface ViewController ()
@property (nonatomic, strong) UICollectionView *collectionView;
@end

collectionView:cellForItemAtIndexPath:

Asks your data source object for the cell that corresponds to the specified item in the collection view.

@implementation ViewController
- (void)viewDidLoad {
	[super viewDidLoad];
	[self loadCollectioView];
}

- (void)loadCollectioView {
    // Layout Init
    // let layout = UICollectionViewFlowLayout()
    UICollectionViewFlowLayout *flowlayout = [[UICollectionViewFlowLayout alloc] init];
    
    // self.collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
    _collectionView = [[UICollectionView alloc]initWithFrame:self.view.frame collectionViewLayout:flowlayout];
    
    // Cell Size
    flowlayout.itemSize = CGSizeMake(280, 280);
    
    // head Size
    flowlayout.headerReferenceSize = CGSizeMake(self.view.frame.size.width, 40);
    
    // Cell vertical spacing
    flowlayout.minimumLineSpacing = 10;
    
    // Cell horizontal spacing
    flowlayout.minimumInteritemSpacing = 20;
    
    // Cell to container edge
    flowlayout.sectionInset = UIEdgeInsetsMake(20, 20, 20, 20); // Up left down right
    
    // collectionView init
    
    // Register Cell !! IMPORTANT
    // collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "cell")
    [_collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:@"cell"];
    
    // Set delegate
    // collectionView.dataSource = self
    // collectionView.delegate = self
    _collectionView.dataSource = self;
    _collectionView.delegate = self;
    
    // Background color
    _collectionView.backgroundColor = [UIColor greenColor];
    
    // Felxible Width & Height
    _collectionView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
    
    // Add View
    [self.view addSubview:_collectionView];
    
    // Size settings
    [_collectionView setFrame:self.view.frame];
}

- (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
    
    // Cell Reuse, identifier is same as the registration(@"cell")
    // let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)
    UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"cell" forIndexPath:indexPath];

    // Optional
    if (!cell) {
        cell = [[UICollectionViewCell alloc] init];
//        [cell.contentView setBackgroundColor:[UIColor systemBlueColor]];
    }
    // cell.contentView.backgroundColor = .systemBlue
    [cell.contentView setBackgroundColor:[UIColor systemBlueColor]];
    return cell;
}

- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
    return 120;
}
@end
import UIKit

class ViewController: UIViewController {
    
    var collectionView: UICollectionView!

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        self.view.backgroundColor = .green
        
        let flowLayout = UICollectionViewFlowLayout()
        self.collectionView = UICollectionView(frame: self.view.safeAreaLayoutGuide.layoutFrame, collectionViewLayout: flowLayout)
        
        flowLayout.scrollDirection = .horizontal
        flowLayout.itemSize = CGSizeMake(180, 180)
        
        collectionView.delegate = self
        collectionView.dataSource = self
        
        collectionView.register(ExampleCell.self, forCellWithReuseIdentifier: String(describing: type(of: ExampleCell.self)))
        
        self.view.addSubview(collectionView)
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
}

extension ViewController: UICollectionViewDataSource {
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: String(describing: type(of: ExampleCell.self)), for: indexPath) as! ExampleCell
        cell.titleLabel.text = String(indexPath.row)
        return cell
    }
    
    func numberOfSections(in collectionView: UICollectionView) -> Int {
        1
    }
    
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        10
    }
}

extension ViewController: UICollectionViewDelegate {
    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        print("Selected item at index: \(indexPath.row)")
    }
}

class ExampleCell: UICollectionViewCell {
    let titleLabel = UILabel()
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        
        self.initUI()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    private func initUI() {
        self.backgroundColor = .red
        
        // Text for index
        titleLabel.frame = CGRect(x: 50, y: 50, width: 50, height: 20)
        self.addSubview(titleLabel)
    }
}

No Storyboard

guard let windowScene = (scene as? UIWindowScene) else { return }
window = UIWindow(frame: windowScene.coordinateSpace.bounds)
window?.windowScene = windowScene
window?.rootViewController = ViewController()
window?.makeKeyAndVisible()

这段代码是使用 Objective-C 编写的。在 Objective-C 中,我们需要显式地进行类型转换,并使用方括号表示方法调用和消息发送的语法。同样,也需要手动创建 UIWindow 对象,并将其设置为应用程序的主窗口。注意,ViewController 是你应用程序中的视图控制器类的名称,你需要根据实际情况进行替换。

UIWindowScene *windowScene = (UIWindowScene *)scene;
if (![windowScene isKindOfClass:[UIWindowScene class]]) {
    return;
}
self.window = [[UIWindow alloc] initWithFrame:windowScene.coordinateSpace.bounds];
self.window.windowScene = windowScene;
self.window.rootViewController = [[ViewController alloc] init];
[self.window makeKeyAndVisible];

Loading Method of AppDelegate, SceneDelegate and Controller AppDelegate、SceneDelegate、控制器的加载方式

iOS13 之前,AppDelegate的职责是:全权处理App生命周期和UI生命周期

Before iOS13, the AppDelegate is responsible for handling the entire App lifecycle and the UI lifecycle.

iOS 13 之后,AppDelegate的职责是:处理App生命周期和SceneDelegate 生命周期

After iOS 13, the AppDelegate is responsible for handling the App lifecycle and the SceneDelegate lifecycle.

StartUp Process 启动过程

main函数 -> 自动释放池 -> UIApplicationMain(永不返回,保证程序不会被销毁)-> 创建应用程序对象UIApplication ->创建应用程序的代理对象AppDelegate -> IOS13之前,将AppDelegate的window实例化,设置为keyWindow主窗口 -> 加载配置文件指定的storyboard

main function -> autorelease pool -> UIApplicationMain (never returns, to ensure that the program will not be destroyed) -> create the application object UIApplication -> create the application delegate object AppDelegate -> IOS13 before, instantiate the AppDelegate’s window, set it as the keyWindow main window -> load the storyboard specified in the configuration file

UIKit UIViewController Lifecycle

graph TD
load --> loadView --> viewDidLoad --> viewWillAppear --> viewWillLayoutSubviews --> viewDidLayoutSubviews --> viewDidAppear
viewDidAppear --> viewWillDisappear --> viewDidDisappear
//
//  ViewController.m
//  DouyinObjc
//
//  Created by Chu Yong on 5/13/23.
//

#import "ViewController.h"

@interface ViewController ()

@end

@implementation ViewController

+ (void)load {
    [super load];
    NSLog(@"1");
}

- (void)loadView {
    [super loadView];
    NSLog(@"2");
}

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    NSLog(@"3");
    
    self.view.backgroundColor = [UIColor redColor];
}

- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    NSLog(@"4");
}

- (void)viewWillLayoutSubviews {
    [super viewWillLayoutSubviews];
    NSLog(@"5");
}

- (void)viewDidLayoutSubviews {
    [super viewDidLayoutSubviews];
    NSLog(@"6");
}

- (void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];
    NSLog(@"7");
}

- (void)viewWillDisappear:(BOOL)animated {
    [super viewWillDisappear:animated];
    NSLog(@"8");
}

- (void)viewDidDisappear:(BOOL)animated {
    [super viewDidDisappear:animated];
    NSLog(@"9");
}

- (void)dealloc {
    NSLog(@"10");
}

@end

MVC

graph LR
UIViewController --> Model
UIViewController --> UIView
Model --X--> UIView
- (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor redColor];
    UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(100, 100, 100, 100)];
    label.text = @"Hello World";
    [self.view addSubview:label];
}

Target for Project in Xcode

<!-- Info.plist -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>UIApplicationSceneManifest</key>
	<dict>
		<key>UIApplicationSupportsMultipleScenes</key>
		<false/>
		<key>UISceneConfigurations</key>
		<dict>
			<key>UIWindowSceneSessionRoleApplication</key>
			<array>
				<dict>
					<key>UISceneConfigurationName</key>
					<string>Default Configuration</string>
					<key>UISceneDelegateClassName</key>
					<string>SceneDelegate</string>
				</dict>
			</array>
		</dict>
	</dict>
</dict>
</plist>

<key>Storyboard Name</key>

Delegate

SwiftUI In UIKit(Objective-C)

import SwiftUI
struct MainViewInterface: View {
     var body: some View {
          Text("Hello, World!")
   }
}

@objc
class MainView: NSObject {
    @objc func makeMainView() -> UIViewController {
        return UIHostingController(rootView:  MainViewInterface())
    }
}
// #import "<projectName>-Swift.h"
// Lookup in Targets - Build Settings - Search for Swift - Objective-C Generated Interface Header Name
#import "testSwift-Swift.h" 

@implementation ViewController

- (void)viewDidLoad {
	[super viewDidLoad];

	UIViewController *vc = [[MainView new] makeMainView];
	vc.view.frame = CGRectMake(100, 400, 200, 100);
	[self.view addSubview:vc.view];
}

@end

Display SwiftUI views from Objective-C codebase

@objc class HelloWorldViewFactory: NSObject {
    
    @objc static func create(text: String) -> UIViewController {
        let helloWorldView = HelloWorldView(text: text)
        let hostingController = UIHostingController(rootView: helloWorldView)
        
        return hostingController
    }
}
UIViewController *vc = [HelloWorldViewFactory createWithText:@"Hello from Obj-C!"];
[self presentViewController:vc animated:YES completion:nil];

Presenting SwiftUI Views from ObjectiveC

import Foundation
import SwiftUI

class SwiftUIViewFactory: NSObject {
  @objc static func makeSwiftUIView(dismissHandler: @escaping (() -> Void)) -> UIViewController {
    return UIHostingController(rootView: SwiftUIView(dismiss: dismissHandler))
  }
}
- (IBAction)showSwiftUIView:(id)sender {
  UIViewController *vc = [SwiftUIViewFactory makeSwiftUIViewWithDismissHandler:^{
    [[self presentedViewController] dismissViewControllerAnimated:YES completion:nil];
  }];
  [self presentViewController:vc animated:YES completion:nil];
}

Full Code

UIButton *btn = [UIButton buttonWithType:UIButtonTypeSystem];
[btn setTitle:@"HI" forState:UIControlStateNormal];
[btn addTarget:self action:@selector(buttonClicked) forControlEvents:UIControlEventTouchUpInside];
btn.frame = CGRectMake(100, 100, 200, 50);
[self.view addSubview:btn];

- (void)buttonClicked {
    UIViewController* vc = [SwiftUIViewFactory makeSwiftUIViewWithDismissHandler:^{
        [[self presentedViewController] dismissViewControllerAnimated:YES completion:nil];
    }];
    [self presentViewController:vc animated:YES completion:nil];
}
class SwiftUIViewFactory: NSObject {
    @objc static func makeSwiftUIView(dismissHandler: @escaping (() -> Void)) -> UIViewController {
        return UIHostingController(rootView: ContentView(dismiss: dismissHandler))
    }
}

Presenting SwiftUI Views from ObjectiveC

Display SwiftUI views from Objective-C codebase

@objc class HelloWorldViewFactory: NSObject {
    
    @objc static func create(text: String) -> UIViewController {
        let helloWorldView = HelloWorldView(text: text)
        let hostingController = UIHostingController(rootView: helloWorldView)
        
        return hostingController
    }
}
UIViewController *vc = [HelloWorldViewFactory createWithText:@"Hello from Obj-C!"];
[self presentViewController:vc animated:YES completion:nil];

Presenting SwiftUI Views from ObjectiveC

import Foundation
import SwiftUI

class SwiftUIViewFactory: NSObject {
  @objc static func makeSwiftUIView(dismissHandler: @escaping (() -> Void)) -> UIViewController {
    return UIHostingController(rootView: SwiftUIView(dismiss: dismissHandler))
  }
}
- (IBAction)showSwiftUIView:(id)sender {
  UIViewController *vc = [SwiftUIViewFactory makeSwiftUIViewWithDismissHandler:^{
    [[self presentedViewController] dismissViewControllerAnimated:YES completion:nil];
  }];
  [self presentViewController:vc animated:YES completion:nil];
}

Full Code

UIButton *btn = [UIButton buttonWithType:UIButtonTypeSystem];
[btn setTitle:@"HI" forState:UIControlStateNormal];
[btn addTarget:self action:@selector(buttonClicked) forControlEvents:UIControlEventTouchUpInside];
btn.frame = CGRectMake(100, 100, 200, 50);
[self.view addSubview:btn];

- (void)buttonClicked {
    UIViewController* vc = [SwiftUIViewFactory makeSwiftUIViewWithDismissHandler:^{
        [[self presentedViewController] dismissViewControllerAnimated:YES completion:nil];
    }];
    [self presentViewController:vc animated:YES completion:nil];
}
class SwiftUIViewFactory: NSObject {
    @objc static func makeSwiftUIView(dismissHandler: @escaping (() -> Void)) -> UIViewController {
        return UIHostingController(rootView: ContentView(dismiss: dismissHandler))
    }
}

Presenting SwiftUI Views from ObjectiveC

Lazy Instantiation

See more in objc-snippet

Border

kingfisher - onevcat on twitter

@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIImageView *imageView;
@property (weak, nonatomic) IBOutlet UIImageView *secondImgView;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    self.imageView.layer.cornerRadius = self.imageView.frame.size.width / 2;
    self.imageView.layer.masksToBounds = true;
    self.imageView.layer.borderWidth = 2.0;
    self.imageView.layer.borderColor = [UIColor whiteColor].CGColor;
    self.secondImgView.layer.cornerRadius = self.imageView.frame.size.width / 2;
    self.secondImgView.layer.borderWidth = 2.0;
    self.secondImgView.layer.borderColor = [UIColor whiteColor].CGColor;
}

@end
import SwiftUI
struct ContentView: View {
    
    var body: some View {
        HStack {
            ForEach(0..<3, id: \.self) { i in
                Image("icon")
                    .resizable()
                    .frame(width: 100, height: 100)
                    .overlay {
                        Circle()
                            .stroke(.blue, lineWidth: 5)
                    }
                //.cornerRadius(50)
                    .offset(x: CGFloat(-i * 50), y: 0)
            }
        }
    }
    
}

在 SwiftUI 中,视图的修改器是一种声明式方法,用于描述视图的外观和行为。在这种情况下,为了实现与 UIKit 类似的效果,我们需要将边框绘制为一个圆形视图的叠加层。

SwiftUI 与 UIKit 的设计理念有很大差异。在 UIKit 中,我们直接操作视图的层(layer)来实现边框、圆角等效果。而在 SwiftUI 中,我们通过组合和修改视图来实现所需的效果。这种声明式的方式允许 SwiftUI 更好地优化渲染过程,并提供更易读、更易维护的代码。

在上面的 SwiftUI 示例中,我们使用了 overlay 修改器来添加一个描边圆形,从而实现了类似于 UIKit 中的边框效果。虽然这种方法与 UIKit 的操作方式不同,但它非常符合 SwiftUI 的设计原则,并能很好地满足需求。

ResponsiveScaleWidth

#define ResponsiveScaleWidth(length) (((length)/375.0f) * [[UIScreen mainScreen] bounds].size.width)

UIKit Preview

UIKit Preview

import UIKit

class ViewController: UINavigationController {

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        var text = UILabel(frame: CGRect(x: 20, y: 20, width: 300, height: 300))
        text.text = "UIKit Preview Testing"
        self.view.addSubview(text)
    }
}

#if DEBUG

import SwiftUI

struct ViewControllerRepresentable: UIViewControllerRepresentable {
    typealias UIViewControllerType = ViewController

    func makeUIViewController(context: Context) -> UIViewControllerType {
        ViewController()
    }
    
    func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {
        
    }
}

struct ViewController_Previews: PreviewProvider {
    static var previews: some View {
        ViewControllerRepresentable()
    }
}

IBAction

- (IBAction)showSwiftUIView:(id)sender {}

这段代码中的圈表示了方法的返回类型。在 Objective-C 中,方法的返回类型用圆括号括起来,并位于方法名称之前。在这种情况下,(IBAction) 表示该方法返回一个特殊的类型,用于在 Interface Builder 中与用户界面的操作进行关联。

IBAction 是 Objective-C 中特有的关键字,用于表示该方法可以与用户界面的事件进行连接。在 Interface Builder 中,你可以将按钮、手势等与该方法关联起来,以在用户交互时触发该方法的执行。

需要注意的是,IBAction 实际上只是一个宏定义,它将被转换为 void 类型,因此在 Swift 中不需要显示地指定返回类型。在 Swift 中,你可以将方法定义为 @IBAction,并省略返回类型的圆括号。例如,在 Swift 中定义一个类似的方法会更简洁:

@IBAction func showSwiftUIView(_ sender: Any) {
    // 方法的具体实现
}

prepareForSegue

#pragma mark - Navigation

// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    // Get the new view controller using [segue destinationViewController].
    // Pass the selected object to the new view controller.
}

prepareForSegue:sender: 方法是在使用基于Storyboard的应用程序中进行页面跳转前的准备工作的常用方法。当一个Segue(故事板中的跳转)被触发时,系统会自动调用该方法。

在这个方法中,你可以通过 [segue destinationViewController] 获取目标视图控制器,然后根据需要进行一些准备工作。例如,你可以将数据传递给目标视图控制器,设置目标视图控制器的属性,或执行其他必要的操作。

通常,你可以根据Segue的标识符(identifier)来区分不同的跳转,并根据需要进行相应的准备工作。例如:

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    if ([segue.identifier isEqualToString:@"MySegueIdentifier"]) {
        // 获取目标视图控制器
        MyViewController *destinationViewController = [segue destinationViewController];
        
        // 进行一些准备工作,例如传递数据或设置属性
        destinationViewController.myProperty = myData;
    }
}

通过在 prepareForSegue:sender: 方法中进行准备工作,你可以确保在进行页面跳转之前,目标视图控制器已经准备好接收数据或执行其他操作,以便在跳转后呈现所需的结果。

头文件 import

在 Objective-C 中,#import 是用于导入头文件的预处理指令。它有两种形式:#import <Framework/Module.h> 和 #import “LocalFile.h”。

#import <Framework/Module.h>:这种形式用于导入系统框架或第三方库的头文件。<Framework/Module.h> 是一个相对于系统或第三方库的头文件路径。这种方式通常用于导入公共框架或库的头文件。例如:

#import <UIKit/UIKit.h>
#import <AFNetworking/AFNetworking.h>

#import “LocalFile.h”:这种形式用于导入项目中的自定义头文件或本地文件的头文件。“LocalFile.h” 是一个相对于当前文件的头文件路径。这种方式通常用于导入自定义的类或其他文件的头文件。例如:

#import "MyCustomClass.h"
#import "Constants.h"

总的来说,#import <Framework/Module.h> 用于导入系统框架或第三方库的头文件,而 #import “LocalFile.h” 用于导入项目中的自定义头文件或本地文件的头文件。

需要注意的是,无论使用哪种形式,#import 指令都会在编译时将指定的头文件内容复制到当前文件中,以便在编译过程中能够访问导入的类、函数、常量等内容。

NSString 判空

NSString 判空的最佳方式

How to detect if NSString is null?

if (str.length == 0) {

NSLog(@"str is empty!!!");

}

这句代码可以通吃上面case1、2、3;其实也是好理解的,nil本身也是一个对象,在ios中给nil

发消息是不会崩溃的,只不过没啥反应而已,因此length也是默认的0了;

至于类似于case4的情况,可以先将字符串中的空格"  " Trim掉,然后在进行判断:

OC中的nil、Nil、NULL、NSNull的区别

SF Symbols

How to use default iOS images?

Present an UIAlertController from an UIView

How to present an UIAlertController from an UIView

Observer 观察者

/// 添加系统通知状态的观察者
- (void)addObserverForSystemNotificationStatus {
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(checkNotificationAuthorization)
                                                 name:UIApplicationWillEnterForegroundNotification
                                               object:nil];
}

MJExtension

iOS 关于项目中经常用到的MJExtension函数

iOS MJExtension使用方法指南(Objective-C)

rootViewController

/// 展示 UIAlertController, Workaround
/// - Parameter alertController:
///
/// [Stack Overflow](https://stackoverflow.com/questions/26554894/how-to-present-uialertcontroller-when-not-in-a-view-controller)
+ (id)rootViewController {
    id rootViewController = [UIApplication sharedApplication].delegate.window.rootViewController;
    if([rootViewController isKindOfClass:[UINavigationController class]])
    {
        rootViewController = ((UINavigationController *)rootViewController).viewControllers.firstObject;
    }
    if([rootViewController isKindOfClass:[UITabBarController class]])
    {
        rootViewController = ((UITabBarController *)rootViewController).selectedViewController;
    }
    return rootViewController;
}

Safe Area

iPhone X Safe Area